diff --git a/Localization/app.json b/Localization/app.json index 3a45922a5..43cdf01ad 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -108,8 +108,7 @@ }, "password": { "placeholder": "password", - "prompt": "Your password needs at least:", - "prompt_eight_characters": "Eight characters" + "hint": "Your password needs at least Eight characters" }, "invite": { "registration_user_invite_request": "Why do you want to join?" diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 9b5894130..2a7c70420 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -72,6 +72,8 @@ 2D927F0E25C7E9C9004F19B8 /* History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F0D25C7E9C9004F19B8 /* History.swift */; }; 2D927F1425C7EDD9004F19B8 /* Emoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D927F1325C7EDD9004F19B8 /* Emoji.swift */; }; 2D939AB525EDD8A90076FA61 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AB425EDD8A90076FA61 /* String.swift */; }; + 2D939AC825EE14620076FA61 /* CropViewController in Frameworks */ = {isa = PBXBuildFile; productRef = 2D939AC725EE14620076FA61 /* CropViewController */; }; + 2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */; }; 2DA7D04425CA52B200804E11 /* TimelineLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D04325CA52B200804E11 /* TimelineLoaderTableViewCell.swift */; }; 2DA7D04A25CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D04925CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift */; }; 2DA7D05725CA693F00804E11 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DA7D05625CA693F00804E11 /* Application.swift */; }; @@ -279,6 +281,7 @@ 2D927F0D25C7E9C9004F19B8 /* History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = History.swift; sourceTree = ""; }; 2D927F1325C7EDD9004F19B8 /* Emoji.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Emoji.swift; sourceTree = ""; }; 2D939AB425EDD8A90076FA61 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; + 2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonRegisterViewController+Avatar.swift"; sourceTree = ""; }; 2DA7D04325CA52B200804E11 /* TimelineLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineLoaderTableViewCell.swift; sourceTree = ""; }; 2DA7D04925CA52CB00804E11 /* TimelineBottomLoaderTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineBottomLoaderTableViewCell.swift; sourceTree = ""; }; 2DA7D05025CA545E00804E11 /* LoadMoreConfigurableTableViewContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreConfigurableTableViewContainer.swift; sourceTree = ""; }; @@ -395,6 +398,7 @@ DB0140BD25C40D7500F9F3CF /* CommonOSLog in Frameworks */, DB89BA0325C10FD0008580ED /* CoreDataStack.framework in Frameworks */, 2D42FF6125C8177C004A627A /* ActiveLabel in Frameworks */, + 2D939AC825EE14620076FA61 /* CropViewController in Frameworks */, 5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */, DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */, 2D61336925C18A4F00CAE157 /* AlamofireNetworkActivityIndicator in Frameworks */, @@ -1079,6 +1083,7 @@ isa = PBXGroup; children = ( DBE0821425CD382600FD6BBD /* MastodonRegisterViewController.swift */, + 2D939AE725EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift */, DBE0822325CD3F1E00FD6BBD /* MastodonRegisterViewModel.swift */, ); path = Register; @@ -1124,6 +1129,7 @@ DB0140BC25C40D7500F9F3CF /* CommonOSLog */, DB5086B725CC0D6400C2C187 /* Kingfisher */, 2D5981B925E4D7F8000FB903 /* ThirdPartyMailer */, + 2D939AC725EE14620076FA61 /* CropViewController */, ); productName = Mastodon; productReference = DB427DD225BAA00100D1B89D /* Mastodon.app */; @@ -1252,6 +1258,7 @@ DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */, DB5086B625CC0D6400C2C187 /* XCRemoteSwiftPackageReference "Kingfisher" */, 2D5981B825E4D7F8000FB903 /* XCRemoteSwiftPackageReference "ThirdPartyMailer" */, + 2D939AC625EE14620076FA61 /* XCRemoteSwiftPackageReference "TOCropViewController" */, ); productRefGroup = DB427DD325BAA00100D1B89D /* Products */; projectDirPath = ""; @@ -1493,6 +1500,7 @@ 2DF75BA125D0E29D00694EC8 /* StatusProvider+TimelinePostTableViewCellDelegate.swift in Sources */, 2D42FF7E25C82218004A627A /* ActionToolBarContainer.swift in Sources */, DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */, + 2D939AE825EE1CF80076FA61 /* MastodonRegisterViewController+Avatar.swift in Sources */, 2D5981A125E4A593000FB903 /* MastodonConfirmEmailViewModel.swift in Sources */, DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */, DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */, @@ -2099,6 +2107,14 @@ minimumVersion = 3.1.0; }; }; + 2D939AC625EE14620076FA61 /* XCRemoteSwiftPackageReference "TOCropViewController" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/TimOliver/TOCropViewController.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.6.0; + }; + }; DB0140BB25C40D7500F9F3CF /* XCRemoteSwiftPackageReference "CommonOSLog" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/MainasuK/CommonOSLog"; @@ -2141,6 +2157,11 @@ package = 2D61336725C18A4F00CAE157 /* XCRemoteSwiftPackageReference "AlamofireNetworkActivityIndicator" */; productName = AlamofireNetworkActivityIndicator; }; + 2D939AC725EE14620076FA61 /* CropViewController */ = { + isa = XCSwiftPackageProductDependency; + package = 2D939AC625EE14620076FA61 /* XCRemoteSwiftPackageReference "TOCropViewController" */; + productName = CropViewController; + }; 5D526FE125BE9AC400460CB9 /* MastodonSDK */ = { isa = XCSwiftPackageProductDependency; productName = MastodonSDK; diff --git a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved index 456a6e967..543a09da9 100644 --- a/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Mastodon.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -90,6 +90,15 @@ "revision": "923c60ee7588da47db8cfc4e0f5b96e5e605ef84", "version": "1.7.1" } + }, + { + "package": "TOCropViewController", + "repositoryURL": "https://github.com/TimOliver/TOCropViewController.git", + "state": { + "branch": null, + "revision": "dad97167bf1be16aeecd109130900995dd01c515", + "version": "2.6.0" + } } ] }, diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index 8e93c804e..dfd69f0c6 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -124,7 +124,7 @@ internal enum L10n { internal static let passwordTooShrot = L10n.tr("Localizable", "Common.Errors.Itemdetail.PasswordTooShrot") /// Username must only contain alphanumeric characters and underscores internal static let usernameInvalid = L10n.tr("Localizable", "Common.Errors.Itemdetail.UsernameInvalid") - /// username is too long ( can't be longer than 30 characters) + /// username is too long (can't be longer than 30 characters) internal static let usernameTooLong = L10n.tr("Localizable", "Common.Errors.Itemdetail.UsernameTooLong") } } @@ -192,12 +192,10 @@ internal enum L10n { internal static let registrationUserInviteRequest = L10n.tr("Localizable", "Scene.Register.Input.Invite.RegistrationUserInviteRequest") } internal enum Password { + /// Your password needs at least Eight characters + internal static let hint = L10n.tr("Localizable", "Scene.Register.Input.Password.Hint") /// password internal static let placeholder = L10n.tr("Localizable", "Scene.Register.Input.Password.Placeholder") - /// Your password needs at least: - internal static let prompt = L10n.tr("Localizable", "Scene.Register.Input.Password.Prompt") - /// Eight characters - internal static let promptEightCharacters = L10n.tr("Localizable", "Scene.Register.Input.Password.PromptEightCharacters") } internal enum Username { /// This username is taken. diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index 191ec0daf..d333460e9 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -42,7 +42,7 @@ "Common.Errors.Itemdetail.EmailInvalid" = "This is not a valid e-mail address"; "Common.Errors.Itemdetail.PasswordTooShrot" = "password is too short (must be at least 8 characters)"; "Common.Errors.Itemdetail.UsernameInvalid" = "Username must only contain alphanumeric characters and underscores"; -"Common.Errors.Itemdetail.UsernameTooLong" = "username is too long ( can't be longer than 30 characters)"; +"Common.Errors.Itemdetail.UsernameTooLong" = "username is too long (can't be longer than 30 characters)"; "Scene.ConfirmEmail.Button.DontReceiveEmail" = "I never got an email"; "Scene.ConfirmEmail.Button.OpenEmailApp" = "Open Email App"; "Scene.ConfirmEmail.DontReceiveEmail.Description" = "Check if your email address is correct as well as your junk folder if you haven’t."; @@ -61,9 +61,8 @@ tap the link to confirm your account."; "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.Hint" = "Your password needs at least Eight characters"; "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.Success" = "Success"; diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController+Avatar.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController+Avatar.swift new file mode 100644 index 000000000..bb4e3f3eb --- /dev/null +++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController+Avatar.swift @@ -0,0 +1,57 @@ +// +// MastodonRegisterViewController+Avatar.swift +// Mastodon +// +// Created by sxiaojian on 2021/3/2. +// + +import CropViewController +import Foundation +import PhotosUI +import UIKit + +extension MastodonRegisterViewController: CropViewControllerDelegate, PHPickerViewControllerDelegate { + func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { + guard let itemProvider = results.first?.itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self) else { + picker.dismiss(animated: true, completion: {}) + return + } + itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in + guard let self = self else { return } + guard let image = image as? UIImage else { + guard let error = error else { return } + let alertController = UIAlertController(for: error, title: "", preferredStyle: .alert) + let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil) + alertController.addAction(okAction) + DispatchQueue.main.async { + self.coordinator.present( + scene: .alertController(alertController: alertController), + from: nil, + transition: .alertController(animated: true, completion: nil) + ) + } + return + } + DispatchQueue.main.async { + let cropController = CropViewController(croppingStyle: .default, image: image) + cropController.delegate = self + cropController.setAspectRatioPreset(.presetSquare, animated: true) + cropController.aspectRatioPickerButtonHidden = true + cropController.aspectRatioLockEnabled = true + picker.dismiss(animated: true, completion: { + self.present(cropController, animated: true, completion: nil) + }) + } + } + } + + public func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) { + self.viewModel.avatarImage.value = image + self.photoButton.setImage(image, for: .normal) + cropViewController.dismiss(animated: true, completion: nil) + } + + @objc func avatarButtonPressed(_ sender: UIButton) { + self.present(imagePicker, animated: true, completion: nil) + } +} diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift index 9931a8b19..a5e75e0fa 100644 --- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift +++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewController.swift @@ -10,6 +10,7 @@ import MastodonSDK import os.log import UIKit import UITextField_Shake +import PhotosUI final class MastodonRegisterViewController: UIViewController, NeedsDependency, OnboardingViewControllerAppearance { var disposeBag = Set() @@ -19,6 +20,15 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O var viewModel: MastodonRegisterViewModel! + lazy var imagePicker: PHPickerViewController = { + var configuration = PHPickerConfiguration() + configuration.filter = .images + + let imagePicker = PHPickerViewController(configuration: configuration) + imagePicker.delegate = self + return imagePicker + }() + let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer let scrollView: UIScrollView = { @@ -56,6 +66,8 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O button.backgroundColor = .white button.layer.cornerRadius = 45 button.clipsToBounds = true + + button.addTarget(self, action: #selector(MastodonRegisterViewController.avatarButtonPressed(_:)), for: .touchUpInside) return button }() diff --git a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel.swift b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel.swift index a32e5d040..f0c11849a 100644 --- a/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel.swift +++ b/Mastodon/Scene/Onboarding/Register/MastodonRegisterViewModel.swift @@ -24,6 +24,7 @@ final class MastodonRegisterViewModel { let email = CurrentValueSubject("") let password = CurrentValueSubject("") let reason = CurrentValueSubject("") + let avatarImage = CurrentValueSubject(nil) // output let approvalRequired: Bool @@ -160,11 +161,8 @@ extension MastodonRegisterViewModel { let falseColor = UIColor.clear let attributeString = NSMutableAttributedString() - let start = NSAttributedString(string: "Your password needs at least:", attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: color]) - attributeString.append(start) - attributeString.append(checkmarkImage(color: eightCharacters ? color : falseColor)) - let eightCharactersDescription = NSAttributedString(string: " Eight characters\n", attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: color]) + let eightCharactersDescription = NSAttributedString(string: L10n.Scene.Register.Input.Password.hint, attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: color]) attributeString.append(eightCharactersDescription) return attributeString