diff --git a/WordPress/Classes/Services/MediaImageService.swift b/WordPress/Classes/Services/MediaImageService.swift index cf13aa7bd3ad..e79eb4c2856b 100644 --- a/WordPress/Classes/Services/MediaImageService.swift +++ b/WordPress/Classes/Services/MediaImageService.swift @@ -24,7 +24,7 @@ final class MediaImageService: NSObject { static func migrateCacheIfNeeded() { let didMigrateKey = "MediaImageService-didMigrateCacheKey" - guard Feature.enabled(.mediaModernization) && !UserDefaults.standard.bool(forKey: didMigrateKey) else { + guard !UserDefaults.standard.bool(forKey: didMigrateKey) else { return } UserDefaults.standard.set(true, forKey: didMigrateKey) diff --git a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift index 36f470dac150..2ed10bd739bf 100644 --- a/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift +++ b/WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift @@ -12,7 +12,6 @@ enum FeatureFlag: Int, CaseIterable { case commentModerationUpdate case compliancePopover case domainFocus - case mediaModernization /// Returns a boolean indicating if the feature is enabled var enabled: Bool { @@ -41,8 +40,6 @@ enum FeatureFlag: Int, CaseIterable { return true case .domainFocus: return true - case .mediaModernization: - return true } } @@ -85,8 +82,6 @@ extension FeatureFlag { return "Compliance Popover" case .domainFocus: return "Domain Focus" - case .mediaModernization: - return "Media Modernization" } } } diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Actions/DashboardQuickActionsCardCell.swift b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Actions/DashboardQuickActionsCardCell.swift index ab57c1f71ed5..124de8b9f203 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Actions/DashboardQuickActionsCardCell.swift +++ b/WordPress/Classes/ViewRelated/Blog/Blog Dashboard/Cards/Quick Actions/DashboardQuickActionsCardCell.swift @@ -98,7 +98,9 @@ final class DashboardQuickActionsCardCell: UICollectionViewCell, Reusable, UITab } case .media: trackQuickActionsEvent(.openedMediaLibrary, blog: blog) - MediaLibraryViewController.showForBlog(blog, from: parentViewController) + let controller = SiteMediaViewController(blog: blog) + parentViewController.show(controller, sender: nil) + QuickStartTourGuide.shared.visited(.mediaScreen) case .stats: trackQuickActionsEvent(.statsAccessed, blog: blog) StatsViewController.show(for: blog, from: parentViewController) diff --git a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m index 551745207255..410ad0734983 100644 --- a/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m +++ b/WordPress/Classes/ViewRelated/Blog/Blog Details/BlogDetailsViewController.m @@ -1827,15 +1827,8 @@ - (void)showPageListFromSource:(BlogDetailsNavigationSource)source - (void)showMediaLibraryFromSource:(BlogDetailsNavigationSource)source { [self trackEvent:WPAnalyticsStatOpenedMediaLibrary fromSource:source]; - if ([Feature enabled:FeatureFlagMediaModernization]) { - SiteMediaViewController *controller = [[SiteMediaViewController alloc] initWithBlog:self.blog]; - [self.presentationDelegate presentBlogDetailsViewController:controller]; - } else { - MediaLibraryViewController *controller = [[MediaLibraryViewController alloc] initWithBlog:self.blog]; - controller.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayModeNever; - [self.presentationDelegate presentBlogDetailsViewController:controller]; - } - + SiteMediaViewController *controller = [[SiteMediaViewController alloc] initWithBlog:self.blog]; + [self.presentationDelegate presentBlogDetailsViewController:controller]; [[QuickStartTourGuide shared] visited:QuickStartTourElementMediaScreen]; } diff --git a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteIconPickerPresenter.swift b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteIconPickerPresenter.swift index 0ff18f108c55..f2d355dc461a 100644 --- a/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteIconPickerPresenter.swift +++ b/WordPress/Classes/ViewRelated/Blog/Site Settings/SiteIconPickerPresenter.swift @@ -138,38 +138,6 @@ extension SiteIconPickerPresenter: ImagePickerControllerDelegate { } } -extension SiteIconPickerPresenter: MediaPickerViewControllerDelegate { - - func mediaPickerControllerDidCancel(_ picker: WPMediaPickerViewController) { - onCompletion?(nil, nil) - } - - /// Retrieves the chosen image and triggers the ImageCropViewController display. - /// - func mediaPickerController(_ picker: WPMediaPickerViewController, didFinishPicking assets: [WPMediaAsset]) { - dataSource = nil - - guard let asset = assets.first else { - return - } - guard let media = asset as? Media else { - assertionFailure("Unsupported asset: \(asset)") - return - } - WPAnalytics.track(.siteSettingsSiteIconGalleryPicked) - - showLoadingMessage() - originalMedia = media - MediaThumbnailCoordinator.shared.thumbnail(for: media, with: CGSize.zero, onCompletion: { [weak self] (image, error) in - guard let image = image else { - self?.showErrorLoadingImageMessage() - return - } - self?.showImageCropViewController(image, presentingViewController: picker) - }) - } -} - extension SiteIconPickerPresenter: SiteMediaPickerViewControllerDelegate { func siteMediaPickerViewController(_ viewController: SiteMediaPickerViewController, didFinishWithSelection selection: [Media]) { guard let media = selection.first else { diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaPickerHelper.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaPickerHelper.swift index dbedf2700c6e..de4f0fed34e5 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaPickerHelper.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaPickerHelper.swift @@ -10,27 +10,11 @@ import UniformTypeIdentifiers public typealias GutenbergMediaPickerHelperCallback = ([Any]?) -> Void -class GutenbergMediaPickerHelper: NSObject { - - fileprivate struct Constants { - static let mediaPickerInsertText = NSLocalizedString( - "Insert %@", - comment: "Button title used in media picker to insert media (photos / videos) into a post. Placeholder will be the number of items that will be inserted." - ) - } - - fileprivate let post: AbstractPost - fileprivate unowned let context: UIViewController - fileprivate weak var navigationPicker: WPNavigationMediaPickerViewController? - fileprivate let noResultsView = NoResultsViewController.controller() +final class GutenbergMediaPickerHelper: NSObject { + private let post: AbstractPost + private unowned let context: UIViewController /// Media Library Data Source - /// - fileprivate lazy var mediaLibraryDataSource: MediaLibraryPickerDataSource = { - let dataSource = MediaLibraryPickerDataSource(post: self.post) - dataSource.ignoreSyncErrors = true - return dataSource - }() var didPickMediaCallback: GutenbergMediaPickerHelperCallback? @@ -48,13 +32,9 @@ class GutenbergMediaPickerHelper: NSObject { case .device: presentNativePicker(filter: filter, allowMultipleSelection: allowMultipleSelection, completion: callback) case .mediaLibrary: - if Feature.enabled(.mediaModernization) { - didPickMediaCallback = callback - MediaPickerMenu(viewController: context, filter: .init(filter), isMultipleSelectionEnabled: allowMultipleSelection) - .showSiteMediaPicker(blog: post.blog, delegate: self) - } else { - presentLegacySiteMediaPicker(filter: filter, allowMultipleSelection: allowMultipleSelection, callback: callback) - } + didPickMediaCallback = callback + MediaPickerMenu(viewController: context, filter: .init(filter), isMultipleSelectionEnabled: allowMultipleSelection) + .showSiteMediaPicker(blog: post.blog, delegate: self) @unknown default: break } @@ -76,28 +56,6 @@ class GutenbergMediaPickerHelper: NSObject { context.present(picker, animated: true) } - private func presentLegacySiteMediaPicker(filter: WPMediaType, - allowMultipleSelection: Bool, - callback: @escaping GutenbergMediaPickerHelperCallback) { - didPickMediaCallback = callback - - let mediaPickerOptions = WPMediaPickerOptions.withDefaults(filter: filter, allowMultipleSelection: allowMultipleSelection) - let picker = WPNavigationMediaPickerViewController(options: mediaPickerOptions) - navigationPicker = picker - - picker.startOnGroupSelector = false - picker.showGroupSelector = false - picker.dataSource = mediaLibraryDataSource - picker.selectionActionTitle = Constants.mediaPickerInsertText - picker.mediaPicker.options = mediaPickerOptions - picker.delegate = self - picker.mediaPicker.registerClass(forCustomHeaderView: DeviceMediaPermissionsHeader.self) - - picker.previewActionTitle = NSLocalizedString("Edit %@", comment: "Button that displays the media editor to the user") - picker.modalPresentationStyle = .currentContext - context.present(picker, animated: true) - } - func presentCameraCaptureFullScreen(animated: Bool, filter: WPMediaType, callback: @escaping GutenbergMediaPickerHelperCallback) { @@ -137,61 +95,8 @@ extension GutenbergMediaPickerHelper: ImagePickerControllerDelegate { } } -// MARK: - User messages for video limits allowances -// extension GutenbergMediaPickerHelper: VideoLimitsAlertPresenter {} -// MARK: - Picker Delegate -// -extension GutenbergMediaPickerHelper: WPMediaPickerViewControllerDelegate, MediaPickerViewControllerDelegate { - - func mediaPickerController(_ picker: WPMediaPickerViewController, didFinishPicking assets: [WPMediaAsset]) { - invokeMediaPickerCallback(asset: assets) - picker.dismiss(animated: true, completion: nil) - } - - open func mediaPickerController(_ picker: WPMediaPickerViewController, handleError error: Error) -> Bool { - let presenter = context.topmostPresentedViewController - let alert = WPMediaPickerAlertHelper.buildAlertControllerWithError(error) - presenter.present(alert, animated: true) - return true - } - - func mediaPickerControllerDidCancel(_ picker: WPMediaPickerViewController) { - mediaLibraryDataSource.searchCancelled() - context.dismiss(animated: true, completion: { self.invokeMediaPickerCallback(asset: nil) }) - } - - fileprivate func invokeMediaPickerCallback(asset: [WPMediaAsset]?) { - didPickMediaCallback?(asset) - didPickMediaCallback = nil - } - - func emptyViewController(forMediaPickerController picker: WPMediaPickerViewController) -> UIViewController? { - guard picker == navigationPicker?.mediaPicker else { - return nil - } - return noResultsView - } - - func mediaPickerController(_ picker: WPMediaPickerViewController, didUpdateSearchWithAssetCount assetCount: Int) { - if (mediaLibraryDataSource.searchQuery?.count ?? 0) > 0 { - noResultsView.configureForNoSearchResult() - } else { - noResultsView.removeFromView() - } - } - - func mediaPickerControllerWillBeginLoadingData(_ picker: WPMediaPickerViewController) { - noResultsView.configureForFetching() - } - - func mediaPickerControllerDidEndLoadingData(_ picker: WPMediaPickerViewController) { - noResultsView.removeFromView() - noResultsView.configureForNoAssets(userCanUploadMedia: false) - } -} - extension GutenbergMediaPickerHelper: SiteMediaPickerViewControllerDelegate { func siteMediaPickerViewController(_ viewController: SiteMediaPickerViewController, didFinishWithSelection selection: [Media]) { context.dismiss(animated: true) @@ -208,26 +113,3 @@ extension GutenbergMediaPickerHelper: PHPickerViewControllerDelegate { didPickMediaCallback = nil } } - -fileprivate extension WPMediaPickerOptions { - static func withDefaults( - showMostRecentFirst: Bool = true, - filter: WPMediaType = [.image], - allowCaptureOfMedia: Bool = false, - showSearchBar: Bool = true, - badgedUTTypes: Set = [UTType.gif.identifier], - allowMultipleSelection: Bool = false, - preferredStatusBarStyle: UIStatusBarStyle = WPStyleGuide.preferredStatusBarStyle - ) -> WPMediaPickerOptions { - let options = WPMediaPickerOptions() - options.showMostRecentFirst = showMostRecentFirst - options.filter = filter - options.allowCaptureOfMedia = allowCaptureOfMedia - options.showSearchBar = showSearchBar - options.badgedUTTypes = badgedUTTypes - options.allowMultipleSelection = allowMultipleSelection - options.preferredStatusBarStyle = preferredStatusBarStyle - - return options - } -} diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift index 5e52fa976d6f..750b8f4c25ff 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift @@ -255,12 +255,6 @@ class GutenbergViewController: UIViewController, PostEditor, FeaturedImageDelega return UInt(currentMetrics.wordCount) } - /// Media Library Data Source - /// - lazy var mediaLibraryDataSource: MediaLibraryPickerDataSource = { - return MediaLibraryPickerDataSource(post: self.post) - }() - // MARK: - Private variables private lazy var gutenbergImageLoader: GutenbergImageLoader = { diff --git a/WordPress/Classes/ViewRelated/Media/MediaLibraryMediaPickingCoordinator.swift b/WordPress/Classes/ViewRelated/Media/MediaLibraryMediaPickingCoordinator.swift deleted file mode 100644 index 50af727aa71a..000000000000 --- a/WordPress/Classes/ViewRelated/Media/MediaLibraryMediaPickingCoordinator.swift +++ /dev/null @@ -1,153 +0,0 @@ -import MobileCoreServices -import WPMediaPicker -import PhotosUI - -/// Prepares the alert controller that will be presented when tapping the "+" button in Media Library -final class MediaLibraryMediaPickingCoordinator { - typealias PickersDelegate = StockPhotosPickerDelegate & WPMediaPickerViewControllerDelegate & TenorPickerDelegate & PHPickerViewControllerDelegate & ImagePickerControllerDelegate - private weak var delegate: PickersDelegate? - private var tenor: TenorPicker? - - private var stockPhotos: StockPhotosPicker? - - init(delegate: PickersDelegate) { - self.delegate = delegate - } - - func present(context: MediaPickingContext) { - let origin = context.origin - let blog = context.blog - let fromView = context.view - let buttonItem = context.barButtonItem - - let menuAlert = UIAlertController(title: nil, message: nil, preferredStyle: UIAlertController.Style.actionSheet) - - if let quotaUsageDescription = blog.quotaUsageDescription { - menuAlert.title = quotaUsageDescription - } - - if UIImagePickerController.isSourceTypeAvailable(.camera) { - menuAlert.addAction(cameraAction(origin: origin, blog: blog)) - } - - menuAlert.addAction(photoLibraryAction(origin: origin, blog: blog)) - - if blog.supports(.stockPhotos) { - menuAlert.addAction(freePhotoAction(origin: origin, blog: blog)) - } - if blog.supports(.tenor) { - menuAlert.addAction(tenorAction(origin: origin, blog: blog)) - } - - menuAlert.addAction(otherAppsAction(origin: origin, blog: blog)) - menuAlert.addAction(cancelAction()) - - menuAlert.popoverPresentationController?.sourceView = fromView - menuAlert.popoverPresentationController?.sourceRect = fromView.bounds - menuAlert.popoverPresentationController?.barButtonItem = buttonItem - - origin.present(menuAlert, animated: true) - } - - private func cameraAction(origin: UIViewController, blog: Blog) -> UIAlertAction { - return UIAlertAction(title: .takePhotoOrVideo, style: .default, handler: { [weak self] action in - self?.showCameraCapture(origin: origin, blog: blog) - }) - } - - private func photoLibraryAction(origin: UIViewController, blog: Blog) -> UIAlertAction { - return UIAlertAction(title: .importFromPhotoLibrary, style: .default, handler: { [weak self] action in - self?.showMediaPicker(origin: origin, blog: blog) - }) - } - - private func freePhotoAction(origin: UIViewController, blog: Blog) -> UIAlertAction { - return UIAlertAction(title: .freePhotosLibrary, style: .default, handler: { [weak self] action in - self?.showStockPhotos(origin: origin, blog: blog) - }) - } - - private func tenorAction(origin: UIViewController, blog: Blog) -> UIAlertAction { - return UIAlertAction(title: .tenor, style: .default, handler: { [weak self] action in - self?.showTenor(origin: origin, blog: blog) - }) - } - - private func otherAppsAction(origin: UIViewController & UIDocumentPickerDelegate, blog: Blog) -> UIAlertAction { - return UIAlertAction(title: .otherApps, style: .default, handler: { [weak self] action in - self?.showDocumentPicker(origin: origin, blog: blog) - }) - } - - private func cancelAction() -> UIAlertAction { - return UIAlertAction(title: .cancelMoreOptions, style: .cancel, handler: nil) - } - - private func showCameraCapture(origin: UIViewController, blog: Blog) { - MediaPickerMenu(viewController: origin) - .showCamera(delegate: self) - } - - private func showStockPhotos(origin: UIViewController, blog: Blog) { - let picker = StockPhotosPicker() - // add delegate conformance, allow release of picker in the same manner as the tenor picker - // in order to prevent duplicated uploads and botched de-selection on second upload - picker.delegate = self - picker.presentPicker(origin: origin, blog: blog) - stockPhotos = picker - } - - private func showTenor(origin: UIViewController, blog: Blog) { - let picker = TenorPicker() - // Delegate to the PickerCoordinator so we can release the Tenor instance - picker.delegate = self - picker.presentPicker(origin: origin, blog: blog) - tenor = picker - } - - - private func showDocumentPicker(origin: UIViewController & UIDocumentPickerDelegate, blog: Blog) { - let docTypes = blog.allowedTypeIdentifiers - let docPicker = UIDocumentPickerViewController(documentTypes: docTypes, in: .import) - docPicker.delegate = origin - docPicker.allowsMultipleSelection = true - origin.present(docPicker, animated: true) - } - - private func showMediaPicker(origin: UIViewController, blog: Blog) { - var configuration = PHPickerConfiguration() - configuration.preferredAssetRepresentationMode = .current - configuration.selection = .ordered - configuration.selectionLimit = 0 // Unlimited - - let picker = PHPickerViewController(configuration: configuration) - picker.delegate = self - origin.present(picker, animated: true) - } -} - -extension MediaLibraryMediaPickingCoordinator: TenorPickerDelegate { - func tenorPicker(_ picker: TenorPicker, didFinishPicking assets: [TenorMedia]) { - delegate?.tenorPicker(picker, didFinishPicking: assets) - tenor = nil - } -} - -extension MediaLibraryMediaPickingCoordinator: StockPhotosPickerDelegate { - func stockPhotosPicker(_ picker: StockPhotosPicker, didFinishPicking assets: [StockPhotosMedia]) { - delegate?.stockPhotosPicker(picker, didFinishPicking: assets) - stockPhotos = nil - } -} - -extension MediaLibraryMediaPickingCoordinator: PHPickerViewControllerDelegate { - func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { - delegate?.picker(picker, didFinishPicking: results) - } -} - -extension MediaLibraryMediaPickingCoordinator: ImagePickerControllerDelegate { - func imagePicker(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { - delegate?.imagePicker(picker, didFinishPickingMediaWithInfo: info) - } -} diff --git a/WordPress/Classes/ViewRelated/Media/MediaLibraryViewController.swift b/WordPress/Classes/ViewRelated/Media/MediaLibraryViewController.swift deleted file mode 100644 index 6fae1678bb56..000000000000 --- a/WordPress/Classes/ViewRelated/Media/MediaLibraryViewController.swift +++ /dev/null @@ -1,832 +0,0 @@ -import UIKit -import Gridicons -import SVProgressHUD -import WordPressShared -import WPMediaPicker -import MobileCoreServices -import UniformTypeIdentifiers -import PhotosUI - -/// Displays the user's media library in a grid -/// -class MediaLibraryViewController: WPMediaPickerViewController { - fileprivate static let restorationIdentifier = "MediaLibraryViewController" - - @objc let blog: Blog - - fileprivate let pickerDataSource: MediaLibraryPickerDataSource - - fileprivate var isLoading: Bool = false - fileprivate let noResultsView = NoResultsViewController.controller() - fileprivate let addButton: SpotlightableButton = SpotlightableButton(type: .custom) - - fileprivate var kvoTokens: [NSKeyValueObservation]? - - fileprivate var selectedAsset: Media? = nil - - // After 99% progress, we'll count a media item as being uploaded, and we'll - // show an indeterminate spinner as the server processes it. - fileprivate static let uploadCompleteProgress: Double = 0.99 - - fileprivate var uploadObserverUUID: UUID? - - fileprivate lazy var mediaPickingCoordinator: MediaLibraryMediaPickingCoordinator = { - return MediaLibraryMediaPickingCoordinator(delegate: self) - }() - - // MARK: - Initializers - - @objc init(blog: Blog) { - WPMediaCollectionViewCell.appearance().placeholderTintColor = .neutral(.shade5) - WPMediaCollectionViewCell.appearance().placeholderBackgroundColor = .neutral(.shade70) - WPMediaCollectionViewCell.appearance().loadingBackgroundColor = .listBackground - - self.blog = blog - self.pickerDataSource = MediaLibraryPickerDataSource(blog: blog) - self.pickerDataSource.includeUnsyncedMedia = true - - super.init(options: MediaLibraryViewController.pickerOptions()) - - registerClass(forReusableCellOverlayViews: CircularProgressView.self) - - super.restorationIdentifier = MediaLibraryViewController.restorationIdentifier - restorationClass = MediaLibraryViewController.self - - self.dataSource = pickerDataSource - self.mediaPickerDelegate = self - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - static func showForBlog(_ blog: Blog, from sourceController: UIViewController) { - if FeatureFlag.mediaModernization.enabled { - let controller = SiteMediaViewController(blog: blog) - sourceController.show(controller, sender: nil) - } else { - let controller = MediaLibraryViewController(blog: blog) - controller.navigationItem.largeTitleDisplayMode = .never - sourceController.navigationController?.pushViewController(controller, animated: true) - } - - QuickStartTourGuide.shared.visited(.mediaScreen) - } - - deinit { - unregisterChangeObserver() - unregisterUploadCoordinatorObserver() - stopObservingNavigationBarClipsToBounds() - } - - private class func pickerOptions() -> WPMediaPickerOptions { - let options = WPMediaPickerOptions() - options.showMostRecentFirst = true - options.filter = [.all] - options.allowMultipleSelection = false - options.allowCaptureOfMedia = false - options.showSearchBar = true - options.showActionBar = false - options.badgedUTTypes = [UTType.gif.identifier] - options.preferredStatusBarStyle = WPStyleGuide.preferredStatusBarStyle - - return options - } - - // MARK: - View Loading - - override func viewDidLoad() { - super.viewDidLoad() - - title = NSLocalizedString("Media", comment: "Title for Media Library section of the app.") - - extendedLayoutIncludesOpaqueBars = true - - registerChangeObserver() - registerUploadCoordinatorObserver() - - noResultsView.configureForNoAssets(userCanUploadMedia: blog.userCanUploadMedia) - noResultsView.delegate = self - - updateViewState(for: pickerDataSource.totalAssetCount) - - if let collectionView = collectionView { - WPStyleGuide.configureColors(view: view, collectionView: collectionView) - } - - navigationController?.navigationBar.subviews.forEach ({ $0.clipsToBounds = false }) - startObservingNavigationBarClipsToBounds() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - selectedAsset = nil - } - - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - - if searchBar?.isFirstResponder == true { - searchBar?.resignFirstResponder() - } - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - addButton.shouldShowSpotlight = QuickStartTourGuide.shared.isCurrentElement(.mediaUpload) - } - - // MARK: - Update view state - - fileprivate func updateViewState(for assetCount: Int) { - updateNavigationItemButtons(for: assetCount) - updateNoResultsView(for: assetCount) - updateSearchBar(for: assetCount) - } - - private func updateNavigationItemButtons(for assetCount: Int) { - if isEditing { - navigationItem.setLeftBarButton(UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(editTapped)), animated: false) - - let trashButton = UIBarButtonItem(image: .gridicon(.trash), style: .plain, target: self, action: #selector(trashTapped)) - trashButton.accessibilityLabel = NSLocalizedString("Trash", comment: "Accessibility label for trash button to delete items from the user's media library") - trashButton.accessibilityHint = NSLocalizedString("Trash selected media", comment: "Accessibility hint for trash button to delete items from the user's media library") - navigationItem.setRightBarButtonItems([trashButton], animated: true) - navigationItem.rightBarButtonItem?.isEnabled = false - } else { - navigationItem.setLeftBarButton(nil, animated: false) - - var barButtonItems = [UIBarButtonItem]() - - if blog.userCanUploadMedia { - addButton.spotlightOffset = Constants.addButtonSpotlightOffset - let config = UIImage.SymbolConfiguration(textStyle: .body, scale: .large) - let image = UIImage(systemName: "plus", withConfiguration: config) ?? .gridicon(.plus) - addButton.setImage(image, for: .normal) - addButton.contentEdgeInsets = Constants.addButtonContentInset - addButton.addTarget(self, action: #selector(addTapped), for: .touchUpInside) - addButton.accessibilityLabel = NSLocalizedString("Add", comment: "Accessibility label for add button to add items to the user's media library") - addButton.accessibilityHint = NSLocalizedString("Add new media", comment: "Accessibility hint for add button to add items to the user's media library") - - let addBarButton = UIBarButtonItem(customView: addButton) - barButtonItems.append(addBarButton) - } - - if blog.supports(.mediaDeletion) && assetCount > 0 { - let editButton = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(editTapped)) - editButton.accessibilityLabel = NSLocalizedString("Edit", comment: "Accessibility label for edit button to enable multi selection mode in the user's media library") - editButton.accessibilityHint = NSLocalizedString("Enter edit mode to enable multi select to delete", comment: "Accessibility hint for edit button to enable multi selection mode in the user's media library") - - barButtonItems.append(editButton) - - } - navigationItem.setRightBarButtonItems(barButtonItems, animated: false) - } - } - - fileprivate func updateNoResultsView(for assetCount: Int) { - - guard assetCount == 0 else { return } - - if isLoading { - noResultsView.configureForFetching() - } else { - noResultsView.removeFromView() - - if hasSearchQuery { - noResultsView.configureForNoSearchResult() - } else { - noResultsView.configureForNoAssets(userCanUploadMedia: blog.userCanUploadMedia) - } - } - } - - private func updateSearchBar(for assetCount: Int) { - let shouldShowBar = hasSearchQuery || assetCount > 0 - - if shouldShowBar { - showSearchBar() - if let searchBar = self.searchBar { - WPStyleGuide.configureSearchBar(searchBar) - } - } else { - hideSearchBar() - } - } - - private func reloadCell(for media: Media) { - visibleCells(for: media).forEach { cell in - cell.overlayView = nil - cell.asset = media - } - } - - private func updateCellProgress(_ progress: Double, for media: Media) { - visibleCells(for: media).forEach { cell in - if let overlayView = cell.overlayView as? CircularProgressView { - if progress < MediaLibraryViewController.uploadCompleteProgress { - overlayView.state = .progress(progress) - } else { - overlayView.state = .indeterminate - } - - configureAppearance(for: overlayView, with: media) - } - } - } - - private func configureAppearance(for overlayView: CircularProgressView, with media: Media) { - if media.localThumbnailURL != nil { - overlayView.backgroundColor = overlayView.backgroundColor?.withAlphaComponent(0.5) - } else { - overlayView.backgroundColor = overlayView.backgroundColor?.withAlphaComponent(1) - } - } - - private func showUploadingStateForCell(for media: Media) { - visibleCells(for: media).forEach { cell in - if let overlayView = cell.overlayView as? CircularProgressView { - overlayView.state = .indeterminate - } - } - } - - private func showFailedStateForCell(for media: Media) { - visibleCells(for: media).forEach { cell in - if let overlayView = cell.overlayView as? CircularProgressView { - overlayView.state = .retry - configureAppearance(for: overlayView, with: media) - } - } - } - - private func visibleCells(for media: Media) -> [WPMediaCollectionViewCell] { - guard let cells = collectionView?.visibleCells as? [WPMediaCollectionViewCell] else { - return [] - } - - return cells.filter({ ($0.asset as? Media) == media }) - } - - private var hasSearchQuery: Bool { - return (pickerDataSource.searchQuery ?? "").count > 0 - } - - // MARK: - Actions - - @objc fileprivate func addTapped() { - QuickStartTourGuide.shared.visited(.mediaUpload) - addButton.shouldShowSpotlight = QuickStartTourGuide.shared.isCurrentElement(.mediaUpload) - showOptionsMenu() - } - - private func showOptionsMenu() { - - let pickingContext: MediaPickingContext - if pickerDataSource.totalAssetCount > 0 { - pickingContext = MediaPickingContext(origin: self, view: view, barButtonItem: navigationItem.rightBarButtonItem, blog: blog) - } else { - pickingContext = MediaPickingContext(origin: self, view: noResultsView.actionButton, blog: blog) - } - - mediaPickingCoordinator.present(context: pickingContext) - } - - @objc private func editTapped() { - isEditing = !isEditing - } - - @objc private func trashTapped() { - let message: String - if selectedAssets.count == 1 { - message = NSLocalizedString("Are you sure you want to permanently delete this item?", comment: "Message prompting the user to confirm that they want to permanently delete a media item. Should match Calypso.") - } else { - message = NSLocalizedString("Are you sure you want to permanently delete these items?", comment: "Message prompting the user to confirm that they want to permanently delete a group of media items.") - } - - let alertController = UIAlertController(title: nil, - message: message, - preferredStyle: .alert) - alertController.addCancelActionWithTitle(NSLocalizedString("Cancel", comment: "Verb. Button title. Tapping cancels an action.")) - alertController.addDestructiveActionWithTitle(NSLocalizedString("Delete", comment: "Title for button that permanently deletes one or more media items (photos / videos)"), handler: { action in - self.deleteSelectedItems() - }) - - present(alertController, animated: true) - } - - private func deleteSelectedItems() { - guard selectedAssets.count > 0 else { return } - guard let assets = selectedAssets as? [Media] else { return } - - let deletedItemsCount = assets.count - - let updateProgress = { (progress: Progress?) in - let fractionCompleted = progress?.fractionCompleted ?? 0 - SVProgressHUD.showProgress(Float(fractionCompleted), status: NSLocalizedString("Deleting...", comment: "Text displayed in HUD while a media item is being deleted.")) - } - - SVProgressHUD.setDefaultMaskType(.clear) - SVProgressHUD.setMinimumDismissTimeInterval(1.0) - - // Initialize the progress HUD before we start - updateProgress(nil) - isEditing = false - - MediaCoordinator.shared.delete(media: assets, - onProgress: updateProgress, - success: { [weak self] in - WPAppAnalytics.track(.mediaLibraryDeletedItems, withProperties: ["number_of_items_deleted": deletedItemsCount], with: self?.blog) - SVProgressHUD.showSuccess(withStatus: NSLocalizedString("Deleted!", comment: "Text displayed in HUD after successfully deleting a media item")) - }, - failure: { - SVProgressHUD.showError(withStatus: NSLocalizedString("Unable to delete all media items.", comment: "Text displayed in HUD if there was an error attempting to delete a group of media items.")) - }) - } - - fileprivate func presentRetryOptions(for media: Media) { - let style: UIAlertController.Style = UIDevice.isPad() ? .alert : .actionSheet - let alertController = UIAlertController(title: nil, message: nil, preferredStyle: style) - alertController.addDestructiveActionWithTitle(NSLocalizedString("Cancel Upload", comment: "Media Library option to cancel an in-progress or failed upload.")) { _ in - MediaCoordinator.shared.delete(media: [media]) - } - - if media.remoteStatus == .failed { - if let error = media.error { - alertController.message = error.localizedDescription - } - if media.canRetry { - alertController.addDefaultActionWithTitle(NSLocalizedString("Retry Upload", comment: "User action to retry media upload.")) { _ in - let info = MediaAnalyticsInfo(origin: .mediaLibrary(.wpMediaLibrary)) - MediaCoordinator.shared.retryMedia(media, analyticsInfo: info) - } - } - } - - alertController.addCancelActionWithTitle( - NSLocalizedString( - "mediaLibrary.retryOptionsAlert.dismissButton", - value: "Dismiss", - comment: "Verb. Button title. Tapping dismisses a prompt." - ) - ) - - present(alertController, animated: true) - } - - override var isEditing: Bool { - didSet { - updateNavigationItemButtons(for: pickerDataSource.totalAssetCount) - let options = self.options.copy() as! WPMediaPickerOptions - options.allowMultipleSelection = isEditing - self.options = options - clearSelectedAssets(false) - } - } - - // MARK: - Media Library Change Observer - - private var mediaLibraryChangeObserverKey: NSObjectProtocol? = nil - - private func registerChangeObserver() { - assert(mediaLibraryChangeObserverKey == nil) - mediaLibraryChangeObserverKey = pickerDataSource.registerChangeObserverBlock({ [weak self] _, removed, inserted, _, _ in - guard let strongSelf = self else { return } - guard removed.count > 0 || inserted.count > 0 else { return } - - strongSelf.updateViewState(for: strongSelf.pickerDataSource.numberOfAssets()) - - if strongSelf.pickerDataSource.totalAssetCount > 0 { - strongSelf.updateNavigationItemButtonsForCurrentAssetSelection() - } else { - strongSelf.isEditing = false - } - - // If we're presenting an item and it's been deleted, pop the - // detail view off the stack - if let navigationController = strongSelf.navigationController, - navigationController.topViewController != strongSelf, - let asset = strongSelf.selectedAsset, - asset.isDeleted { - _ = strongSelf.navigationController?.popToViewController(strongSelf, animated: true) - } - }) - } - - private func unregisterChangeObserver() { - if let mediaLibraryChangeObserverKey = mediaLibraryChangeObserverKey { - pickerDataSource.unregisterChangeObserver(mediaLibraryChangeObserverKey) - } - } - - // MARK: - Upload Coordinator Observer - - private func registerUploadCoordinatorObserver() { - uploadObserverUUID = MediaCoordinator.shared.addObserver({ [weak self] (media, state) in - switch state { - case .progress(let progress): - if media.remoteStatus == .failed { - self?.showFailedStateForCell(for: media) - } else { - self?.updateCellProgress(progress, for: media) - } - case .processing, .uploading: - self?.showUploadingStateForCell(for: media) - case .ended: - self?.reloadCell(for: media) - case .failed: - self?.showFailedStateForCell(for: media) - case .thumbnailReady: - if media.remoteStatus == .failed { - self?.showFailedStateForCell(for: media) - } else { - self?.showUploadingStateForCell(for: media) - } - } - }, for: nil) - } - - private func unregisterUploadCoordinatorObserver() { - if let uuid = uploadObserverUUID { - MediaCoordinator.shared.removeObserver(withUUID: uuid) - } - } - - // MARK: ClipsToBounds KVO Observer - - /// The content view of the navigation bar causes the spotlight view on the add button to be clipped. - /// This ensures that `clipsToBounds` of the content view is always `false`. - /// Without this, `clipsToBounds` reverts to `true` at some point during the view lifecycle. This happens asynchronously, - /// so we can't confidently reset it. Hence the need for KVO. - private func startObservingNavigationBarClipsToBounds() { - kvoTokens = navigationController?.navigationBar.subviews.map({ subview in - return subview.observe(\.clipsToBounds, options: .new, changeHandler: { view, change in - guard let newValue = change.newValue, newValue else { return } - view.clipsToBounds = false - }) - }) - } - - private func stopObservingNavigationBarClipsToBounds() { - kvoTokens?.forEach({ $0.invalidate() }) - } -} - -// MARK: - PHPickerViewControllerDelegate - -extension MediaLibraryViewController: PHPickerViewControllerDelegate { - func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { - dismiss(animated: true) - - for result in results { - let info = MediaAnalyticsInfo(origin: .mediaLibrary(.deviceLibrary), selectionMethod: .fullScreenPicker) - MediaCoordinator.shared.addMedia(from: result.itemProvider, to: blog, analyticsInfo: info) - } - } -} - -// MARK: - ImagePickerControllerDelegate - -extension MediaLibraryViewController: ImagePickerControllerDelegate { - func imagePicker(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { - dismiss(animated: true) - - func addAsset(from exportableAsset: ExportableAsset) { - let info = MediaAnalyticsInfo(origin: .mediaLibrary(.camera), selectionMethod: .fullScreenPicker) - MediaCoordinator.shared.addMedia(from: exportableAsset, to: blog, analyticsInfo: info) - } - - guard let mediaType = info[.mediaType] as? String else { - return - } - switch mediaType { - case UTType.image.identifier: - if let image = info[.originalImage] as? UIImage { - addAsset(from: image) - } - - case UTType.movie.identifier: - guard let videoURL = info[.mediaURL] as? URL else { - return - } - guard self.blog.canUploadVideo(from: videoURL) else { - self.presentVideoLimitExceededAfterCapture(on: self) - return - } - addAsset(from: videoURL as NSURL) - default: - break - } - } -} - - -// MARK: - UIDocumentPickerDelegate - -extension MediaLibraryViewController: UIDocumentPickerDelegate { - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - for documentURL in urls as [NSURL] { - let info = MediaAnalyticsInfo(origin: .mediaLibrary(.otherApps), selectionMethod: .documentPicker) - MediaCoordinator.shared.addMedia(from: documentURL, to: blog, analyticsInfo: info) - } - } - - func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { - dismiss(animated: true) - } -} - -// MARK: - NoResultsViewControllerDelegate - -extension MediaLibraryViewController: NoResultsViewControllerDelegate { - func actionButtonPressed() { - addTapped() - } -} - -// MARK: - User messages for video limits allowances -extension MediaLibraryViewController: VideoLimitsAlertPresenter {} - -// MARK: - WPMediaPickerViewControllerDelegate - -extension MediaLibraryViewController: WPMediaPickerViewControllerDelegate { - - func emptyViewController(forMediaPickerController picker: WPMediaPickerViewController) -> UIViewController? { - return noResultsView - } - - func mediaPickerController(_ picker: WPMediaPickerViewController, didUpdateSearchWithAssetCount assetCount: Int) { - updateNoResultsView(for: assetCount) - } - - func mediaPickerController(_ picker: WPMediaPickerViewController, didFinishPicking assets: [WPMediaAsset]) { - // We're only interested in the upload picker - guard picker != self else { return } - pickerDataSource.searchCancelled() - - dismiss(animated: true) - - guard ReachabilityUtils.isInternetReachable() else { - ReachabilityUtils.showAlertNoInternetConnection() - return - } - - guard let assets = assets as? [PHAsset], - assets.count > 0 else { return } - - for asset in assets { - let info = MediaAnalyticsInfo(origin: .mediaLibrary(.deviceLibrary), selectionMethod: .fullScreenPicker) - MediaCoordinator.shared.addMedia(from: asset, to: blog, analyticsInfo: info) - } - } - - func mediaPickerControllerDidCancel(_ picker: WPMediaPickerViewController) { - pickerDataSource.searchCancelled() - - dismiss(animated: true) - } - - func mediaPickerController(_ picker: WPMediaPickerViewController, willShowOverlayView overlayView: UIView, forCellFor asset: WPMediaAsset) { - guard let overlayView = overlayView as? CircularProgressView, - let media = asset as? Media else { - return - } - WPStyleGuide.styleProgressViewForMediaCell(overlayView) - switch media.remoteStatus { - case .processing: - if let progress = MediaCoordinator.shared.progress(for: media) { - overlayView.state = .progress(progress.fractionCompleted) - } else { - overlayView.state = .indeterminate - } - case .pushing: - if let progress = MediaCoordinator.shared.progress(for: media) { - overlayView.state = .progress(progress.fractionCompleted) - } - case .failed: - overlayView.state = .retry - default: break - } - configureAppearance(for: overlayView, with: media) - } - - func mediaPickerController(_ picker: WPMediaPickerViewController, shouldShowOverlayViewForCellFor asset: WPMediaAsset) -> Bool { - if picker != self, !blog.canUploadAsset(asset) { - return true - } - if let media = asset as? Media { - return media.remoteStatus != .sync - } - - return false - } - - func mediaPickerControllerShouldShowCustomHeaderView(_ picker: WPMediaPickerViewController) -> Bool { - guard picker != self else { - return false - } - - // Show the device media permissions header if photo library access is limited - return PHPhotoLibrary.authorizationStatus(for: .readWrite) == .limited - } - - func mediaPickerControllerReferenceSize(forCustomHeaderView picker: WPMediaPickerViewController) -> CGSize { - let header = DeviceMediaPermissionsHeader() - header.translatesAutoresizingMaskIntoConstraints = false - - return header.referenceSizeInView(picker.view) - } - - func mediaPickerController(_ picker: WPMediaPickerViewController, configureCustomHeaderView headerView: UICollectionReusableView) { - guard let headerView = headerView as? DeviceMediaPermissionsHeader else { - return - } - - headerView.presenter = picker - } - - func mediaPickerController(_ picker: WPMediaPickerViewController, previewViewControllerFor asset: WPMediaAsset) -> UIViewController? { - guard picker == self else { return WPAssetViewController(asset: asset) } - - guard let media = asset as? Media, - media.remoteStatus == .sync else { - return nil - } - - WPAppAnalytics.track(.mediaLibraryPreviewedItem, with: blog) - return mediaItemViewController(for: asset) - } - - func mediaPickerController(_ picker: WPMediaPickerViewController, shouldSelect asset: WPMediaAsset) -> Bool { - if picker != self, !blog.canUploadAsset(asset) { - presentVideoLimitExceededFromPicker(on: picker) - return false - } - - guard picker == self else { - return true - } - - guard let media = asset as? Media else { - return false - } - - guard !isEditing else { - return media.remoteStatus == .sync || media.remoteStatus == .failed - } - - switch media.remoteStatus { - case .failed, .pushing, .processing: - presentRetryOptions(for: media) - case .sync: - if let viewController = mediaItemViewController(for: asset) { - WPAppAnalytics.track(.mediaLibraryPreviewedItem, with: blog) - navigationController?.pushViewController(viewController, animated: true) - } - default: break - } - - return false - } - - func mediaPickerController(_ picker: WPMediaPickerViewController, didSelect asset: WPMediaAsset) { - guard picker == self else { return } - - updateNavigationItemButtonsForCurrentAssetSelection() - } - - func mediaPickerController(_ picker: WPMediaPickerViewController, didDeselect asset: WPMediaAsset) { - guard picker == self else { return } - - updateNavigationItemButtonsForCurrentAssetSelection() - } - - @objc func updateNavigationItemButtonsForCurrentAssetSelection() { - if isEditing { - // Check that our selected items haven't been deleted – we're notified - // of changes to the data source before the collection view has - // updated its selected assets. - guard let assets = (selectedAssets as? [Media]) else { return } - let existingAssets = assets.filter({ !$0.isDeleted }) - - navigationItem.rightBarButtonItem?.isEnabled = (existingAssets.count > 0) - } - } - - private func mediaItemViewController(for asset: WPMediaAsset) -> UIViewController? { - if isEditing { return nil } - - guard let asset = asset as? Media else { - return nil - } - - selectedAsset = asset - - return MediaItemViewController(media: asset) - } - - func mediaPickerControllerWillBeginLoadingData(_ picker: WPMediaPickerViewController) { - guard picker == self else { return } - - isLoading = true - - updateNoResultsView(for: pickerDataSource.numberOfAssets()) - } - - func mediaPickerControllerDidEndLoadingData(_ picker: WPMediaPickerViewController) { - guard picker == self else { return } - - isLoading = false - - updateViewState(for: pickerDataSource.numberOfAssets()) - } - - func mediaPickerController(_ picker: WPMediaPickerViewController, handleError error: Error) -> Bool { - guard picker == self else { return false } - - let nserror = error as NSError - if let mediaLibrary = self.blog.media, !mediaLibrary.isEmpty { - let title = NSLocalizedString("Unable to Sync", comment: "Title of error prompt shown when a sync the user initiated fails.") - WPError.showNetworkingNotice(title: title, error: nserror) - } - return true - } -} - -// MARK: - State restoration - -extension MediaLibraryViewController: UIViewControllerRestoration { - enum EncodingKey { - static let blogURL = "blogURL" - } - - static func viewController(withRestorationIdentifierPath identifierComponents: [String], - coder: NSCoder) -> UIViewController? { - guard let identifier = identifierComponents.last, - identifier == MediaLibraryViewController.restorationIdentifier else { - return nil - } - - guard let blogURL = coder.decodeObject(forKey: EncodingKey.blogURL) as? URL else { - return nil - } - - let context = ContextManager.sharedInstance().mainContext - guard let objectID = context.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: blogURL), - let object = try? context.existingObject(with: objectID), - let blog = object as? Blog else { - return nil - } - return MediaLibraryViewController(blog: blog) - } - - override func encodeRestorableState(with coder: NSCoder) { - super.encodeRestorableState(with: coder) - - coder.encode(blog.objectID.uriRepresentation(), forKey: EncodingKey.blogURL) - } -} - -// MARK: Stock Photos Picker Delegate - -extension MediaLibraryViewController: StockPhotosPickerDelegate { - func stockPhotosPicker(_ picker: StockPhotosPicker, didFinishPicking assets: [StockPhotosMedia]) { - guard assets.count > 0 else { - return - } - - let mediaCoordinator = MediaCoordinator.shared - assets.forEach { stockPhoto in - let info = MediaAnalyticsInfo(origin: .mediaLibrary(.stockPhotos), selectionMethod: .fullScreenPicker) - mediaCoordinator.addMedia(from: stockPhoto, to: blog, analyticsInfo: info) - WPAnalytics.track(.stockMediaUploaded) - } - } -} - -// MARK: Tenor Picker Delegate - -extension MediaLibraryViewController: TenorPickerDelegate { - func tenorPicker(_ picker: TenorPicker, didFinishPicking assets: [TenorMedia]) { - guard assets.count > 0 else { - return - } - - let mediaCoordinator = MediaCoordinator.shared - assets.forEach { tenorMedia in - let info = MediaAnalyticsInfo(origin: .mediaLibrary(.tenor), selectionMethod: .fullScreenPicker) - mediaCoordinator.addMedia(from: tenorMedia, to: blog, analyticsInfo: info) - WPAnalytics.track(.tenorUploaded) - } - } -} - -// MARK: Constants - -extension MediaLibraryViewController { - private enum Constants { - static let addButtonSpotlightOffset = UIOffset(horizontal: 20, vertical: -10) - static let addButtonContentInset = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0) - } -} diff --git a/WordPress/Classes/ViewRelated/Media/MediaPickerMenu.swift b/WordPress/Classes/ViewRelated/Media/MediaPickerMenu.swift index 2d27875bc587..c3b81f8b66f1 100644 --- a/WordPress/Classes/ViewRelated/Media/MediaPickerMenu.swift +++ b/WordPress/Classes/ViewRelated/Media/MediaPickerMenu.swift @@ -175,7 +175,7 @@ extension MediaPickerMenu { extension MediaPickerMenu { /// Returns an action for selecting media from the media uploaded by the user /// to their site. - func makeSiteMediaAction(blog: Blog, delegate: MediaPickerViewControllerDelegate & SiteMediaPickerViewControllerDelegate) -> UIAction { + func makeSiteMediaAction(blog: Blog, delegate: SiteMediaPickerViewControllerDelegate) -> UIAction { UIAction( title: Strings.pickFromMedia, image: UIImage(systemName: "photo.stack"), @@ -184,15 +184,7 @@ extension MediaPickerMenu { ) } - func showSiteMediaPicker(blog: Blog, delegate: MediaPickerViewControllerDelegate & SiteMediaPickerViewControllerDelegate) { - if Feature.enabled(.mediaModernization) { - showModernSiteMediaPicker(blog: blog, delegate: delegate) - } else { - showLegacySiteMediaPicker(blog: blog, delegate: delegate) - } - } - - private func showModernSiteMediaPicker(blog: Blog, delegate: SiteMediaPickerViewControllerDelegate) { + func showSiteMediaPicker(blog: Blog, delegate: SiteMediaPickerViewControllerDelegate) { let viewController = SiteMediaPickerViewController( blog: blog, filter: filter.map { [$0.mediaType] }, @@ -202,66 +194,8 @@ extension MediaPickerMenu { let navigation = UINavigationController(rootViewController: viewController) presentingViewController?.present(navigation, animated: true) } - - private func showLegacySiteMediaPicker(blog: Blog, delegate: MediaPickerViewControllerDelegate) { - let options = WPMediaPickerOptions() - options.showMostRecentFirst = true - if let filter { - switch filter { - case .images: - options.filter = [.image] - case .videos: - options.filter = [.video] - } - } - options.allowMultipleSelection = isMultipleSelectionEnabled - options.showSearchBar = true - options.badgedUTTypes = [UTType.gif.identifier] - options.preferredStatusBarStyle = WPStyleGuide.preferredStatusBarStyle - options.allowCaptureOfMedia = false - - let dataSource = MediaLibraryPickerDataSource(blog: blog) - dataSource.ignoreSyncErrors = true - - let delegate = PickerMenuMediaPickerViewControllerDelegate(delegate: delegate) - - let picker = WPNavigationMediaPickerViewController(options: options) - picker.showGroupSelector = false - picker.dataSource = dataSource - picker.delegate = delegate - picker.modalPresentationStyle = .formSheet - - objc_setAssociatedObject(picker, &MediaPickerMenu.dataSourceAssociatedKey, dataSource, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - objc_setAssociatedObject(picker, &MediaPickerMenu.delegateAssociatedKey, delegate, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - - presentingViewController?.present(picker, animated: true) - } - - private static var dataSourceAssociatedKey: UInt8 = 0 - private static var delegateAssociatedKey: UInt8 = 0 -} - -/// Exposes only a subset of `WPMediaPickerViewControllerDelegate` to the users. -protocol MediaPickerViewControllerDelegate: AnyObject { - func mediaPickerController(_ picker: WPMediaPickerViewController, didFinishPicking assets: [WPMediaAsset]) - func mediaPickerControllerDidCancel(_ picker: WPMediaPickerViewController) } -private final class PickerMenuMediaPickerViewControllerDelegate: NSObject, WPMediaPickerViewControllerDelegate { - weak var delegate: MediaPickerViewControllerDelegate? - - init(delegate: MediaPickerViewControllerDelegate) { - self.delegate = delegate - } - - func mediaPickerController(_ picker: WPMediaPickerViewController, didFinishPicking assets: [WPMediaAsset]) { - delegate?.mediaPickerController(picker, didFinishPicking: assets) - } - - func mediaPickerControllerDidCancel(_ picker: WPMediaPickerViewController) { - delegate?.mediaPickerControllerDidCancel(picker) - } -} // MARK: - MediaPickerMenu (Stock Photo) @@ -285,6 +219,8 @@ extension MediaPickerMenu { objc_setAssociatedObject(stockPhotosViewController, &MediaPickerMenu.dataSourceAssociatedKey, picker, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } + + private static var dataSourceAssociatedKey: UInt8 = 0 } // MARK: - MediaPickerMenu (Free GIF, Tenor) diff --git a/WordPress/Classes/ViewRelated/Post/PostEditor.swift b/WordPress/Classes/ViewRelated/Post/PostEditor.swift index 9659e966968d..9742f66a8956 100644 --- a/WordPress/Classes/ViewRelated/Post/PostEditor.swift +++ b/WordPress/Classes/ViewRelated/Post/PostEditor.swift @@ -80,9 +80,6 @@ protocol PostEditor: PublishingEditor, UIViewControllerTransitioningDelegate { /// Returns true if the site mode is on var isSingleSiteMode: Bool { get } - /// MediaLibraryPickerDataSource - var mediaLibraryDataSource: MediaLibraryPickerDataSource { get set } - /// Returns the media attachment removed version of html func contentByStrippingMediaAttachments() -> String diff --git a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+FeaturedImageUpload.swift b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+FeaturedImageUpload.swift index 4bde063a339a..8d75d2903bdc 100644 --- a/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+FeaturedImageUpload.swift +++ b/WordPress/Classes/ViewRelated/Post/PostSettingsViewController+FeaturedImageUpload.swift @@ -55,25 +55,6 @@ extension PostSettingsViewController: PHPickerViewControllerDelegate, ImagePicke } } -extension PostSettingsViewController: MediaPickerViewControllerDelegate { - func mediaPickerController(_ picker: WPMediaPickerViewController, didFinishPicking assets: [WPMediaAsset]) { - guard !assets.isEmpty else { return } - - WPAnalytics.track(.editorPostFeaturedImageChanged, properties: ["via": "settings", "action": "added"]) - - if let media = assets.first as? Media { - setFeaturedImage(media: media) - } - - dismiss(animated: true) - reloadFeaturedImageCell() - } - - func mediaPickerControllerDidCancel(_ picker: WPMediaPickerViewController) { - dismiss(animated: true) - } -} - extension PostSettingsViewController: SiteMediaPickerViewControllerDelegate { func siteMediaPickerViewController(_ viewController: SiteMediaPickerViewController, didFinishWithSelection selection: [Media]) { dismiss(animated: true) diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 8a14e55a635b..07b77da3d784 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -724,7 +724,6 @@ 17B7C89E20EC1D0D0042E260 /* UniversalLinkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B7C89D20EC1D0D0042E260 /* UniversalLinkRouter.swift */; }; 17B7C8A020EC1D6A0042E260 /* Route.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B7C89F20EC1D6A0042E260 /* Route.swift */; }; 17B7C8C120EE2A870042E260 /* Routes+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B7C8C020EE2A870042E260 /* Routes+Notifications.swift */; }; - 17BB26AE1E6D8321008CD031 /* MediaLibraryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17BB26AD1E6D8321008CD031 /* MediaLibraryViewController.swift */; }; 17BD4A0820F76A4700975AC3 /* Routes+Banners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17BD4A0720F76A4700975AC3 /* Routes+Banners.swift */; }; 17BD4A192101D31B00975AC3 /* NavigationActionHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17BD4A182101D31B00975AC3 /* NavigationActionHelpers.swift */; }; 17C1D67C2670E3DC006C8970 /* SiteIconPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C1D67B2670E3DC006C8970 /* SiteIconPickerView.swift */; }; @@ -3074,7 +3073,6 @@ D0E2AA7C4D4CB1679173958E /* Pods_WordPressShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 213A62FF811EBDB969FA7669 /* Pods_WordPressShareExtension.framework */; }; D8071631203DA23700B32FD9 /* Accessible.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8071630203DA23700B32FD9 /* Accessible.swift */; }; D809E686203F0215001AA0DE /* OldReaderPostCardCellTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D809E685203F0215001AA0DE /* OldReaderPostCardCellTests.swift */; }; - D80BC79C207464D200614A59 /* MediaLibraryMediaPickingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80BC79B207464D200614A59 /* MediaLibraryMediaPickingCoordinator.swift */; }; D80BC79E20746B4100614A59 /* MediaPickingContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80BC79D20746B4100614A59 /* MediaPickingContext.swift */; }; D80BC7A22074739400614A59 /* MediaLibraryStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80BC7A12074739300614A59 /* MediaLibraryStrings.swift */; }; D81322B32050F9110067714D /* NotificationName+Names.swift in Sources */ = {isa = PBXBuildFile; fileRef = D81322B22050F9110067714D /* NotificationName+Names.swift */; }; @@ -5125,7 +5123,6 @@ FABB24E52602FC2C00C8785C /* CoreDataIterativeMigrator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FD83CBE246C751800381999 /* CoreDataIterativeMigrator.swift */; }; FABB24E62602FC2C00C8785C /* ReaderSiteSearchViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17CE77EE20C6CDAA001DEA5A /* ReaderSiteSearchViewController.swift */; }; FABB24E82602FC2C00C8785C /* ReaderPost.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D42A3DD175E7452005CFF05 /* ReaderPost.m */; }; - FABB24E92602FC2C00C8785C /* MediaLibraryMediaPickingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D80BC79B207464D200614A59 /* MediaLibraryMediaPickingCoordinator.swift */; }; FABB24EA2602FC2C00C8785C /* PageTemplateLayout+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46183CF2251BD658004F9AFD /* PageTemplateLayout+CoreDataClass.swift */; }; FABB24EB2602FC2C00C8785C /* NoResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 982A4C3420227D6700B5518E /* NoResultsViewController.swift */; }; FABB24EC2602FC2C00C8785C /* ActionDispatcherFacade.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57C3D392235DFD8E00FE9CE6 /* ActionDispatcherFacade.swift */; }; @@ -5221,7 +5218,6 @@ FABB254D2602FC2C00C8785C /* WPStyleGuide+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B56D3119AFB68800B4E29B /* WPStyleGuide+Notifications.swift */; }; FABB254E2602FC2C00C8785C /* SettingTableViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FF8DDCDE1B5DB1C10098826F /* SettingTableViewCell.m */; }; FABB254F2602FC2C00C8785C /* CountriesMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3BDA0D22944F3500FBF510 /* CountriesMapView.swift */; }; - FABB25502602FC2C00C8785C /* MediaLibraryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17BB26AD1E6D8321008CD031 /* MediaLibraryViewController.swift */; }; FABB25512602FC2C00C8785C /* TodayWidgetStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98E58A2E2360D23400E5534B /* TodayWidgetStats.swift */; }; FABB25522602FC2C00C8785C /* GutenbergMediaFilesUploadProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E1577E25DE04E200EEEDFB /* GutenbergMediaFilesUploadProcessor.swift */; }; FABB25532602FC2C00C8785C /* WordPress-30-31.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = 5DF7F7731B22337C003A05C8 /* WordPress-30-31.xcmappingmodel */; }; @@ -6391,7 +6387,6 @@ 17B7C89F20EC1D6A0042E260 /* Route.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Route.swift; sourceTree = ""; }; 17B7C8C020EE2A870042E260 /* Routes+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Routes+Notifications.swift"; sourceTree = ""; }; 17BABCA52124487600B86ADF /* WordPress 79.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "WordPress 79.xcdatamodel"; sourceTree = ""; }; - 17BB26AD1E6D8321008CD031 /* MediaLibraryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaLibraryViewController.swift; sourceTree = ""; }; 17BD4A0720F76A4700975AC3 /* Routes+Banners.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Routes+Banners.swift"; sourceTree = ""; }; 17BD4A182101D31B00975AC3 /* NavigationActionHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationActionHelpers.swift; sourceTree = ""; }; 17C1D67B2670E3DC006C8970 /* SiteIconPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiteIconPickerView.swift; sourceTree = ""; }; @@ -8448,7 +8443,6 @@ D67306CD28F2440FF6B0065C /* Pods-JetpackIntents.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JetpackIntents.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-JetpackIntents/Pods-JetpackIntents.debug.xcconfig"; sourceTree = ""; }; D8071630203DA23700B32FD9 /* Accessible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessible.swift; sourceTree = ""; }; D809E685203F0215001AA0DE /* OldReaderPostCardCellTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OldReaderPostCardCellTests.swift; sourceTree = ""; }; - D80BC79B207464D200614A59 /* MediaLibraryMediaPickingCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLibraryMediaPickingCoordinator.swift; sourceTree = ""; }; D80BC79D20746B4100614A59 /* MediaPickingContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPickingContext.swift; sourceTree = ""; }; D80BC7A12074739300614A59 /* MediaLibraryStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaLibraryStrings.swift; sourceTree = ""; }; D81322B22050F9110067714D /* NotificationName+Names.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationName+Names.swift"; sourceTree = ""; }; @@ -12595,14 +12589,12 @@ B5EEB19E1CA96D19004B6540 /* ImageCropViewController.xib */, FF945F6E1B28242300FB8AC4 /* MediaLibraryPickerDataSource.h */, FF945F6F1B28242300FB8AC4 /* MediaLibraryPickerDataSource.m */, - 17BB26AD1E6D8321008CD031 /* MediaLibraryViewController.swift */, 173B215427875E0600D4DD6B /* DeviceMediaPermissionsHeader.swift */, 1782BE831E70063100A91E7D /* MediaItemViewController.swift */, 177074841FB209F100951A4A /* CircularProgressView.swift */, 981D0929211259840014ECAF /* NoResultsViewController+MediaLibrary.swift */, 17D5C3F61FFCF2D800EB70FF /* MediaProgressCoordinatorNoticeViewModel.swift */, 1750BD6C201144DB0050F13A /* MediaNoticeNavigationCoordinator.swift */, - D80BC79B207464D200614A59 /* MediaLibraryMediaPickingCoordinator.swift */, D80BC7A12074739300614A59 /* MediaLibraryStrings.swift */, 0C8FC9A02A8BC8630059DCE4 /* PHPickerController+Extensions.swift */, 0C8FC9A62A8BFAAD0059DCE4 /* NSItemProvider+Exportable.swift */, @@ -22518,7 +22510,6 @@ FEA7948D26DD136700CEC520 /* CommentHeaderTableViewCell.swift in Sources */, C3C39B0726F50D3900B1238D /* WordPressSupportSourceTag+Editor.swift in Sources */, 80A2154329D1177A002FE8EB /* RemoteConfigDebugViewController.swift in Sources */, - D80BC79C207464D200614A59 /* MediaLibraryMediaPickingCoordinator.swift in Sources */, 46183CF4251BD658004F9AFD /* PageTemplateLayout+CoreDataClass.swift in Sources */, 982A4C3520227D6700B5518E /* NoResultsViewController.swift in Sources */, 57C3D393235DFD8E00FE9CE6 /* ActionDispatcherFacade.swift in Sources */, @@ -22669,7 +22660,6 @@ 01B5C3C72AE7FC61007055BB /* UITestConfigurator.swift in Sources */, FF8DDCDF1B5DB1C10098826F /* SettingTableViewCell.m in Sources */, 9A3BDA0E22944F3500FBF510 /* CountriesMapView.swift in Sources */, - 17BB26AE1E6D8321008CD031 /* MediaLibraryViewController.swift in Sources */, 98E58A2F2360D23400E5534B /* TodayWidgetStats.swift in Sources */, F5E1577F25DE04E200EEEDFB /* GutenbergMediaFilesUploadProcessor.swift in Sources */, 80EF672227F160720063B138 /* DashboardCustomAnnouncementCell.swift in Sources */, @@ -25371,7 +25361,6 @@ F4D82972293109A600038726 /* DashboardMigrationSuccessCell+Jetpack.swift in Sources */, 83A337A22A9FA525009ED60C /* ReaderSiteHeaderView.swift in Sources */, FABB24E82602FC2C00C8785C /* ReaderPost.m in Sources */, - FABB24E92602FC2C00C8785C /* MediaLibraryMediaPickingCoordinator.swift in Sources */, CECEEB562823164800A28ADE /* MediaCacheSettingsViewController.swift in Sources */, FABB24EA2602FC2C00C8785C /* PageTemplateLayout+CoreDataClass.swift in Sources */, FABB24EB2602FC2C00C8785C /* NoResultsViewController.swift in Sources */, @@ -25517,7 +25506,6 @@ FABB254F2602FC2C00C8785C /* CountriesMapView.swift in Sources */, FAD257132611B04D00EDAF88 /* UIColor+JetpackColors.swift in Sources */, 4A1E77C7298897F6006281CC /* SharingSyncService.swift in Sources */, - FABB25502602FC2C00C8785C /* MediaLibraryViewController.swift in Sources */, FABB25512602FC2C00C8785C /* TodayWidgetStats.swift in Sources */, FABB25522602FC2C00C8785C /* GutenbergMediaFilesUploadProcessor.swift in Sources */, FABB25532602FC2C00C8785C /* WordPress-30-31.xcmappingmodel in Sources */,