Merge pull request #76 from tootsuite/feature/avatarSelect

feature: Add context menu for avatar select for sign up page
This commit is contained in:
sxiaojian88 2021-04-01 09:59:55 +08:00 committed by GitHub
commit 21b50da2bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 134 additions and 28 deletions

View File

@ -108,6 +108,9 @@
"register": {
"title": "Tell us about you.",
"input": {
"avatar": {
"delete": "delete"
},
"username": {
"placeholder": "username",
"duplicate_prompt": "This username is taken."

View File

@ -105,7 +105,7 @@
"repositoryURL": "https://github.com/MainasuK/TwitterTextEditor",
"state": {
"branch": "feature/input-view",
"revision": "03e7b7497d424d96268f5bcca1f8e9955bb80fea",
"revision": "1e565d13e3c26fc2bedeb418890df42f80d6e3d5",
"version": null
}
},

View File

@ -339,6 +339,10 @@ internal enum L10n {
}
}
internal enum Input {
internal enum Avatar {
/// delete
internal static let delete = L10n.tr("Localizable", "Scene.Register.Input.Avatar.Delete")
}
internal enum DisplayName {
/// display name
internal static let placeholder = L10n.tr("Localizable", "Scene.Register.Input.DisplayName.Placeholder")

View File

@ -106,6 +106,7 @@ tap the link to confirm your account.";
"Scene.Register.Error.Special.PasswordTooShort" = "Password is too short (must be at least 8 characters)";
"Scene.Register.Error.Special.UsernameInvalid" = "Username must only contain alphanumeric characters and underscores";
"Scene.Register.Error.Special.UsernameTooLong" = "Username is too long (can't be longer than 30 characters)";
"Scene.Register.Input.Avatar.Delete" = "delete";
"Scene.Register.Input.DisplayName.Placeholder" = "display name";
"Scene.Register.Input.Email.Placeholder" = "email";
"Scene.Register.Input.Invite.RegistrationUserInviteRequest" = "Why do you want to join?";

View File

@ -98,7 +98,7 @@ final class ComposeViewController: UIViewController, NeedsDependency {
}()
private(set) lazy var documentPickerController: UIDocumentPickerViewController = {
let documentPickerController = UIDocumentPickerViewController(documentTypes: ["public.image"], in: .open)
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: [.image])
documentPickerController.delegate = self
return documentPickerController
}()

View File

@ -7,10 +7,57 @@
import CropViewController
import Foundation
import OSLog
import PhotosUI
import UIKit
extension MastodonRegisterViewController {
func createMediaContextMenu() -> UIMenu {
var children: [UIMenuElement] = []
let photoLibraryAction = UIAction(title: L10n.Scene.Compose.MediaSelection.photoLibrary, image: UIImage(systemName: "rectangle.on.rectangle"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
guard let self = self else { return }
self.present(self.imagePicker, animated: true, completion: nil)
}
children.append(photoLibraryAction)
if UIImagePickerController.isSourceTypeAvailable(.camera) {
let cameraAction = UIAction(title: L10n.Scene.Compose.MediaSelection.camera, image: UIImage(systemName: "camera"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off, handler: { [weak self] _ in
guard let self = self else { return }
self.present(self.imagePickerController, animated: true, completion: nil)
})
children.append(cameraAction)
}
let browseAction = UIAction(title: L10n.Scene.Compose.MediaSelection.browse, image: UIImage(systemName: "ellipsis"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
guard let self = self else { return }
self.present(self.documentPickerController, animated: true, completion: nil)
}
children.append(browseAction)
if self.viewModel.avatarImage.value != nil {
let deleteAction = UIAction(title: L10n.Scene.Register.Input.Avatar.delete, image: UIImage(systemName: "delete.left"), identifier: nil, discoverabilityTitle: nil, attributes: [], state: .off) { [weak self] _ in
guard let self = self else { return }
self.viewModel.avatarImage.value = nil
}
children.append(deleteAction)
}
return UIMenu(title: "", image: nil, identifier: nil, options: .displayInline, children: children)
}
private func cropImage(image:UIImage,pickerViewController:UIViewController) {
DispatchQueue.main.async {
let cropController = CropViewController(croppingStyle: .default, image: image)
cropController.delegate = self
cropController.setAspectRatioPreset(.presetSquare, animated: true)
cropController.aspectRatioPickerButtonHidden = true
cropController.aspectRatioLockEnabled = true
pickerViewController.dismiss(animated: true, completion: {
self.present(cropController, animated: true, completion: nil)
})
}
}
}
// MARK: - PHPickerViewControllerDelegate
extension MastodonRegisterViewController: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
guard let itemProvider = results.first?.itemProvider, itemProvider.canLoadObject(ofClass: UIImage.self) else {
@ -20,11 +67,11 @@ extension MastodonRegisterViewController: PHPickerViewControllerDelegate {
itemProvider.loadObject(ofClass: UIImage.self) { [weak self] image, error in
guard let self = self else { return }
guard let image = image as? UIImage else {
DispatchQueue.main.async {
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,
@ -33,31 +80,52 @@ extension MastodonRegisterViewController: PHPickerViewControllerDelegate {
}
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)
})
self.cropImage(image: image, pickerViewController: picker)
}
}
}
// MARK: - UIImagePickerControllerDelegate
extension MastodonRegisterViewController: UIImagePickerControllerDelegate & UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
picker.dismiss(animated: true, completion: nil)
guard let image = info[.originalImage] as? UIImage else { return }
cropImage(image: image, pickerViewController: picker)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
os_log("%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
picker.dismiss(animated: true, completion: nil)
}
}
// MARK: - UIDocumentPickerDelegate
extension MastodonRegisterViewController: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
guard let url = urls.first else { return }
do {
guard url.startAccessingSecurityScopedResource() else { return }
defer { url.stopAccessingSecurityScopedResource() }
let imageData = try Data(contentsOf: url)
guard let image = UIImage(data: imageData) else { return }
cropImage(image: image, pickerViewController: controller)
} catch {
os_log("%{public}s[%{public}ld], %{public}s: %s", (#file as NSString).lastPathComponent, #line, #function, error.localizedDescription)
}
}
}
// MARK: - CropViewControllerDelegate
extension MastodonRegisterViewController: CropViewControllerDelegate {
public func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) {
self.viewModel.avatarImage.value = image
self.avatarButton.setImage(image, for: .normal)
cropViewController.dismiss(animated: true, completion: nil)
}
}
extension MastodonRegisterViewController {
@objc func avatarButtonPressed(_ sender: UIButton) {
self.present(imagePicker, animated: true, completion: nil)
}
}

View File

@ -20,14 +20,28 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
var viewModel: MastodonRegisterViewModel!
lazy var imagePicker: PHPickerViewController = {
// picker
private(set) lazy var imagePicker: PHPickerViewController = {
var configuration = PHPickerConfiguration()
configuration.filter = .images
configuration.selectionLimit = 1
let imagePicker = PHPickerViewController(configuration: configuration)
imagePicker.delegate = self
return imagePicker
}()
private(set) lazy var imagePickerController: UIImagePickerController = {
let imagePickerController = UIImagePickerController()
imagePickerController.sourceType = .camera
imagePickerController.delegate = self
return imagePickerController
}()
private(set) lazy var documentPickerController: UIDocumentPickerViewController = {
let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: [.image])
documentPickerController.delegate = self
return documentPickerController
}()
let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer
@ -56,7 +70,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
}()
let avatarButton: UIButton = {
let button = UIButton(type: .custom)
let button = HighlightDimmableButton()
let boldFont = UIFont.systemFont(ofSize: 42)
let configuration = UIImage.SymbolConfiguration(font: boldFont)
let image = UIImage(systemName: "person.fill.viewfinder", withConfiguration: configuration)
@ -227,6 +241,9 @@ extension MastodonRegisterViewController {
setupOnboardingAppearance()
defer { setupNavigationBarBackgroundView() }
avatarButton.menu = createMediaContextMenu()
avatarButton.showsMenuAsPrimaryAction = true
domainLabel.text = "@" + viewModel.domain + " "
domainLabel.sizeToFit()
passwordCheckLabel.attributedText = MastodonRegisterViewModel.attributeStringForPassword(validateState: .empty)
@ -388,9 +405,8 @@ extension MastodonRegisterViewController {
.receive(on: DispatchQueue.main)
.sink { [weak self] isHighlighted in
guard let self = self else { return }
let alpha: CGFloat = isHighlighted ? 0.8 : 1
let alpha: CGFloat = isHighlighted ? 0.6 : 1
self.plusIconImageView.alpha = alpha
self.avatarButton.alpha = alpha
}
.store(in: &disposeBag)
@ -484,6 +500,21 @@ extension MastodonRegisterViewController {
}
.store(in: &disposeBag)
viewModel.avatarImage
.receive(on: DispatchQueue.main)
.sink{ [weak self] image in
guard let self = self else { return }
self.avatarButton.menu = self.createMediaContextMenu()
if let avatar = image {
self.avatarButton.setImage(avatar, for: .normal)
} else {
let boldFont = UIFont.systemFont(ofSize: 42)
let configuration = UIImage.SymbolConfiguration(font: boldFont)
let image = UIImage(systemName: "person.fill.viewfinder", withConfiguration: configuration)
self.avatarButton.setImage(image?.withRenderingMode(UIImage.RenderingMode.alwaysTemplate), for: UIControl.State.normal)
}
}
.store(in: &disposeBag)
NotificationCenter.default
.publisher(for: UITextField.textDidChangeNotification, object: usernameTextField)
.receive(on: DispatchQueue.main)
@ -550,7 +581,6 @@ extension MastodonRegisterViewController {
.store(in: &disposeBag)
}
avatarButton.addTarget(self, action: #selector(MastodonRegisterViewController.avatarButtonPressed(_:)), for: .touchUpInside)
signUpButton.addTarget(self, action: #selector(MastodonRegisterViewController.signUpButtonPressed(_:)), for: .touchUpInside)
}