Skip to content

Commit

Permalink
Merge pull request #21370 from wordpress-mobile/task/phpicker-site-icon
Browse files Browse the repository at this point in the history
Integrate PHPickerViewController in Site Icon picker
  • Loading branch information
kean authored Aug 21, 2023
2 parents d264d82 + 5eccbcd commit a6b45fd
Show file tree
Hide file tree
Showing 8 changed files with 419 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import Gridicons
import UIKit

@objc protocol BlogDetailHeaderViewDelegate {
func siteIconTapped()
func makeSiteIconMenu() -> UIMenu?
func didShowSiteIconMenu()
func siteIconReceivedDroppedImage(_ image: UIImage?)
func siteIconShouldAllowDroppedImages() -> Bool
func siteTitleTapped()
Expand Down Expand Up @@ -107,11 +108,12 @@ class BlogDetailHeaderView: UIView {

// MARK: - Initializers

required init(items: [ActionRow.Item]) {
required init(items: [ActionRow.Item], delegate: BlogDetailHeaderViewDelegate) {
titleView = TitleView(frame: .zero)

super.init(frame: .zero)

self.delegate = delegate
setupChildViews(items: items)
}

Expand All @@ -122,11 +124,14 @@ class BlogDetailHeaderView: UIView {
// MARK: - Child View Initialization

private func setupChildViews(items: [ActionRow.Item]) {
titleView.siteIconView.tapped = { [weak self] in
QuickStartTourGuide.shared.visited(.siteIcon)
self?.titleView.siteIconView.spotlightIsShown = false
assert(delegate != nil)

self?.delegate?.siteIconTapped()
if let menu = delegate?.makeSiteIconMenu() {
titleView.siteIconView.setMenu(menu) { [weak self] in
self?.delegate?.didShowSiteIconMenu()
WPAnalytics.track(.siteSettingsSiteIconTapped)
self?.titleView.siteIconView.spotlightIsShown = false
}
}

titleView.siteIconView.dropped = { [weak self] images in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ class SiteIconView: UIView {

private var dropInteraction: UIDropInteraction?

/// Set the menu to be displayed when the button is tapped. The menu replaces
/// teh default on tap action.
func setMenu(_ menu: UIMenu, onMenuTriggered: @escaping () -> Void) {
button.menu = menu
button.showsMenuAsPrimaryAction = true
button.addAction(UIAction { _ in onMenuTriggered() }, for: .menuActionTriggered)
}

private let button: UIButton = {
let button = UIButton(frame: .zero)
button.backgroundColor = UIColor.secondaryButtonBackground
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ extension SitePickerViewController {
blogDetailHeaderView.toggleSpotlightOnSiteIcon()
}

private func showNoticeAsNeeded() {
func showNoticeAsNeeded() {
if let tourToSuggest = QuickStartTourGuide.shared.tourToSuggest(for: blog) {
QuickStartTourGuide.shared.suggest(tourToSuggest, for: blog)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,73 +3,79 @@ import WordPressFlux
import WordPressShared
import SwiftUI
import SVProgressHUD
import Gridicons
import PhotosUI

extension SitePickerViewController {

func showSiteIconSelectionAlert() {
let alert = UIAlertController(title: SiteIconAlertStrings.title,
message: nil,
preferredStyle: .actionSheet)

alert.popoverPresentationController?.sourceView = blogDetailHeaderView.blavatarImageView.superview
alert.popoverPresentationController?.sourceRect = blogDetailHeaderView.blavatarImageView.frame
alert.popoverPresentationController?.permittedArrowDirections = .any

alert.addDefaultActionWithTitle(SiteIconAlertStrings.Actions.chooseImage) { [weak self] _ in
NoticesDispatch.unlock()
self?.updateSiteIcon()
}

alert.addDefaultActionWithTitle(SiteIconAlertStrings.Actions.createWithEmoji) { [weak self] _ in
NoticesDispatch.unlock()
self?.showEmojiPicker()
func makeSiteIconMenu() -> UIMenu? {
guard siteIconShouldAllowDroppedImages() else {
return nil
}

alert.addDestructiveActionWithTitle(SiteIconAlertStrings.Actions.removeSiteIcon) { [weak self] _ in
NoticesDispatch.unlock()
self?.removeSiteIcon()
}

alert.addCancelActionWithTitle(SiteIconAlertStrings.Actions.cancel) { [weak self] _ in
NoticesDispatch.unlock()
self?.startAlertTimer()
}

present(alert, animated: true)
return UIMenu(children: [
UIDeferredMenuElement.uncached { [weak self] in
$0(self?.makeUpdateSiteIconActions() ?? [])
}
])
}

func showUpdateSiteIconAlert() {
let alert = UIAlertController(title: nil,
message: nil,
preferredStyle: .actionSheet)

alert.popoverPresentationController?.sourceView = blogDetailHeaderView.blavatarImageView.superview
alert.popoverPresentationController?.sourceRect = blogDetailHeaderView.blavatarImageView.frame
alert.popoverPresentationController?.permittedArrowDirections = .any

alert.addDefaultActionWithTitle(SiteIconAlertStrings.Actions.changeSiteIcon) { [weak self] _ in
NoticesDispatch.unlock()
self?.updateSiteIcon()
}

if blog.hasIcon {
alert.addDestructiveActionWithTitle(SiteIconAlertStrings.Actions.removeSiteIcon) { [weak self] _ in
NoticesDispatch.unlock()
self?.removeSiteIcon()
func didShowSiteIconMenu() {
if QuickStartTourGuide.shared.isCurrentElement(.siteIcon) {
// There is no good way to determine when `UIMenu` is cancelled,
// so we wait until nothing is presented by the site picker.
NoticesDispatch.lock()
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
if self.presentedViewController == nil {
NoticesDispatch.unlock()
self.showNoticeAsNeeded()
timer.invalidate()
}
}
}
QuickStartTourGuide.shared.visited(.siteIcon)
}

alert.addCancelActionWithTitle(SiteIconAlertStrings.Actions.cancel) { [weak self] _ in
NoticesDispatch.unlock()
self?.startAlertTimer()
private func makeUpdateSiteIconActions() -> [UIAction] {
let presenter = makeSiteIconPresenter()
let mediaMenu = MediaPickerMenu(viewConroller: self, filter: .images)
var actions: [UIAction] = []
if FeatureFlag.nativePhotoPicker.enabled {
actions += [
mediaMenu.makePhotosAction(delegate: presenter),
mediaMenu.makeCameraAction(delegate: presenter),
mediaMenu.makeMediaAction(blog: blog, delegate: presenter)
]
} else {
actions.append(UIAction(
title: SiteIconAlertStrings.Actions.changeSiteIcon,
image: UIImage(systemName: "photo.on.rectangle"),
handler: { [weak self] _ in
guard let self else { return }
presenter.presentPickerFrom(self)
}
))
}

present(alert, animated: true)
if FeatureFlag.siteIconCreator.enabled {
actions.append(UIAction(
title: SiteIconAlertStrings.Actions.createWithEmoji,
image: UIImage(systemName: "face.smiling"),
handler: { [weak self] _ in self?.showEmojiPicker() }
))
}
if blog.hasIcon {
actions.append(UIAction(
title: SiteIconAlertStrings.Actions.removeSiteIcon,
image: UIImage(systemName: "trash"),
attributes: [.destructive],
handler: { [weak self] _ in self?.removeSiteIcon() }
))
}
return actions
}

func updateSiteIcon() {
siteIconPickerPresenter = SiteIconPickerPresenter(blog: blog)
siteIconPickerPresenter?.onCompletion = { [ weak self] media, error in
private func makeSiteIconPresenter() -> SiteIconPickerPresenter {
let presenter = SiteIconPickerPresenter(blog: blog)
presenter.onCompletion = { [ weak self] media, error in
if error != nil {
self?.showErrorForSiteIconUpdate()
} else if let media = media {
Expand All @@ -82,13 +88,12 @@ extension SitePickerViewController {
self?.siteIconPickerPresenter = nil
self?.startAlertTimer()
}

siteIconPickerPresenter?.onIconSelection = { [weak self] in
presenter.onIconSelection = { [weak self] in
self?.blogDetailHeaderView.updatingIcon = true
self?.dismiss(animated: true)
}

siteIconPickerPresenter?.presentPickerFrom(self)
self.siteIconPickerPresenter = presenter
return presenter
}

func showEmojiPicker() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class SitePickerViewController: UIViewController {
let mediaService: MediaService

private(set) lazy var blogDetailHeaderView: BlogDetailHeaderView = {
let headerView = BlogDetailHeaderView(items: [])
let headerView = BlogDetailHeaderView(items: [], delegate: self)
headerView.translatesAutoresizingMaskIntoConstraints = false
return headerView
}()
Expand Down Expand Up @@ -51,7 +51,6 @@ final class SitePickerViewController: UIViewController {

private func setupHeaderView() {
blogDetailHeaderView.blog = blog
blogDetailHeaderView.delegate = self
view.addSubview(blogDetailHeaderView)
view.pinSubviewToAllEdges(blogDetailHeaderView)
}
Expand All @@ -71,25 +70,6 @@ final class SitePickerViewController: UIViewController {

extension SitePickerViewController: BlogDetailHeaderViewDelegate {

func siteIconTapped() {
guard siteIconShouldAllowDroppedImages() else {
// Gracefully ignore the tap for users that can not upload files or
// blogs that do not have capabilities since those will not support the REST API icon update
return
}

WPAnalytics.track(.siteSettingsSiteIconTapped)

NoticesDispatch.lock()

guard FeatureFlag.siteIconCreator.enabled else {
showUpdateSiteIconAlert()
return
}

showSiteIconSelectionAlert()
}

func siteIconReceivedDroppedImage(_ image: UIImage?) {
if !siteIconShouldAllowDroppedImages() {
// Gracefully ignore the drop for users that can not upload files or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import WPMediaPicker
import WordPressShared
import MobileCoreServices
import UniformTypeIdentifiers
import PhotosUI

/// Encapsulates the interactions required to capture a new site icon image, crop it and resize it.
///
class SiteIconPickerPresenter: NSObject {
final class SiteIconPickerPresenter: NSObject {

// MARK: - Public Properties

Expand Down Expand Up @@ -48,6 +49,9 @@ class SiteIconPickerPresenter: NSObject {
return pickerViewController
}()

private var dataSource: AnyObject?
private var mediaCapturePresenter: AnyObject?

// MARK: - Public methods

/// Designated Initializer
Expand Down Expand Up @@ -87,7 +91,7 @@ class SiteIconPickerPresenter: NSObject {

/// Shows a new ImageCropViewController for the given image.
///
fileprivate func showImageCropViewController(_ image: UIImage) {
func showImageCropViewController(_ image: UIImage, presentingViewController: UIViewController? = nil) {
DispatchQueue.main.async {
SVProgressHUD.dismiss()
let imageCropViewController = ImageCropViewController(image: image)
Expand Down Expand Up @@ -131,7 +135,17 @@ class SiteIconPickerPresenter: NSObject {
}
}
}
self.mediaPickerViewController.show(after: imageCropViewController)
if let presentingViewController {
imageCropViewController.shouldShowCancelButton = true
imageCropViewController.onCancel = { [weak presentingViewController] in
// Dismiss the crop controller but not the picker
presentingViewController?.dismiss(animated: true)
}
let navigationController = UINavigationController(rootViewController: imageCropViewController)
presentingViewController.present(navigationController, animated: true)
} else {
self.mediaPickerViewController.show(after: imageCropViewController)
}
}
}

Expand Down Expand Up @@ -173,6 +187,39 @@ class SiteIconPickerPresenter: NSObject {
}
}

extension SiteIconPickerPresenter: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
guard let result = results.first else {
picker.presentingViewController?.dismiss(animated: true)
return
}
WPAnalytics.track(.siteSettingsSiteIconGalleryPicked)
self.showLoadingMessage()
self.originalMedia = nil
MediaPickerMenu.loadImage(for: result) { [weak self] image, error in
if let image {
self?.showImageCropViewController(image, presentingViewController: picker)
} else {
DDLogError("Failed to load image: \(String(describing: error))")
self?.showErrorLoadingImageMessage()
}
}
}
}

extension SiteIconPickerPresenter: ImagePickerControllerDelegate {
func imagePicker(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
guard let presentingViewController = picker.presentingViewController else {
return
}
presentingViewController.dismiss(animated: true) {
if let image = info[.originalImage] as? UIImage {
self.showImageCropViewController(image, presentingViewController: presentingViewController)
}
}
}
}

extension SiteIconPickerPresenter: WPMediaPickerViewControllerDelegate {

func mediaPickerControllerWillBeginLoadingData(_ picker: WPMediaPickerViewController) {
Expand Down Expand Up @@ -206,6 +253,7 @@ extension SiteIconPickerPresenter: WPMediaPickerViewControllerDelegate {
/// Retrieves the chosen image and triggers the ImageCropViewController display.
///
func mediaPickerController(_ picker: WPMediaPickerViewController, didFinishPicking assets: [WPMediaAsset]) {
dataSource = nil
mediaLibraryDataSource.searchCancelled()
if assets.isEmpty {
return
Expand Down Expand Up @@ -240,7 +288,11 @@ extension SiteIconPickerPresenter: WPMediaPickerViewControllerDelegate {
self?.showErrorLoadingImageMessage()
return
}
self?.showImageCropViewController(image)
if FeatureFlag.nativePhotoPicker.enabled {
self?.showImageCropViewController(image, presentingViewController: picker)
} else {
self?.showImageCropViewController(image)
}
})
default:
break
Expand Down
Loading

0 comments on commit a6b45fd

Please sign in to comment.