diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift index cb96490fe411..b43f9f266817 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergViewController.swift @@ -22,6 +22,9 @@ class GutenbergViewController: UIViewController, PostEditor { private lazy var filesAppMediaPicker: GutenbergFilesAppMediaSource = { return GutenbergFilesAppMediaSource(gutenberg: gutenberg, mediaInserter: mediaInserterHelper) }() + private lazy var tenorMediaPicker: GutenbergTenorMediaPicker = { + return GutenbergTenorMediaPicker(gutenberg: gutenberg, mediaInserter: mediaInserterHelper) + }() // MARK: - Aztec @@ -378,8 +381,14 @@ extension GutenbergViewController: GutenbergBridgeDelegate { gutenbergDidRequestMediaFromDevicePicker(filter: flags, allowMultipleSelection: allowMultipleSelection, with: callback) case .deviceCamera: gutenbergDidRequestMediaFromCameraPicker(filter: flags, with: callback) + case .stockPhotos: stockPhotos.presentPicker(origin: self, post: post, multipleSelection: allowMultipleSelection, callback: callback) + case .tenor: + tenorMediaPicker.presentPicker(origin: self, + post: post, + multipleSelection: allowMultipleSelection, + callback: callback) case .filesApp: filesAppMediaPicker.presentPicker(origin: self, filters: filter, multipleSelection: allowMultipleSelection, callback: callback) default: break @@ -637,6 +646,7 @@ extension GutenbergViewController: GutenbergBridgeDataSource { func gutenbergMediaSources() -> [Gutenberg.MediaSource] { return [ post.blog.supports(.stockPhotos) ? .stockPhotos : nil, + FeatureFlag.tenor.enabled ? .tenor : nil, .filesApp, ].compactMap { $0 } } @@ -738,6 +748,7 @@ extension GutenbergViewController: PostEditorNavigationBarManagerDelegate { extension Gutenberg.MediaSource { static let stockPhotos = Gutenberg.MediaSource(id: "wpios-stock-photo-library", label: .freePhotosLibrary, types: [.image]) static let filesApp = Gutenberg.MediaSource(id: "wpios-files-app", label: .files, types: [.image, .video, .audio, .other]) + static let tenor = Gutenberg.MediaSource(id: "wpios-tenor", label: .tenor, types: [.image]) } private extension GutenbergViewController { diff --git a/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergTenorMediaPicker.swift b/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergTenorMediaPicker.swift new file mode 100644 index 000000000000..b54b77f8c668 --- /dev/null +++ b/WordPress/Classes/ViewRelated/Gutenberg/Utils/GutenbergTenorMediaPicker.swift @@ -0,0 +1,81 @@ +import Gutenberg + +class GutenbergTenorMediaPicker { + private var tenor: TenorPicker? + private var mediaPickerCallback: MediaPickerDidPickMediaCallback? + private let mediaInserter: GutenbergMediaInserterHelper + private unowned var gutenberg: Gutenberg + private var multipleSelection = false + + init(gutenberg: Gutenberg, mediaInserter: GutenbergMediaInserterHelper) { + self.mediaInserter = mediaInserter + self.gutenberg = gutenberg + } + + func presentPicker(origin: UIViewController, post: AbstractPost, multipleSelection: Bool, callback: @escaping MediaPickerDidPickMediaCallback) { + let picker = TenorPicker() + tenor = picker + picker.allowMultipleSelection = true + picker.delegate = self + mediaPickerCallback = callback + picker.presentPicker(origin: origin, blog: post.blog) + self.multipleSelection = multipleSelection + } +} + +extension GutenbergTenorMediaPicker: TenorPickerDelegate { + func tenorPicker(_ picker: TenorPicker, didFinishPicking assets: [TenorMedia]) { + defer { + mediaPickerCallback = nil + tenor = nil + } + guard assets.isEmpty == false else { + mediaPickerCallback?(nil) + return + } + + // For blocks that support multiple uploads this will upload all images. + // If multiple uploads are not supported then it will seperate them out to Image Blocks. + multipleSelection ? insertOnBlock(with: assets) : insertSingleImages(assets) + } + + /// Adds the given image object to the requesting block and seperates multiple images to seperate image blocks + /// - Parameter asset: Tenor Media object to add. + func insertSingleImages(_ assets: [TenorMedia]) { + // Append the first item via callback given by Gutenberg. + if let firstItem = assets.first { + insertOnBlock(with: [firstItem]) + } + // Append the rest of images via `.appendMedia` event. + // Ideally we would send all picked images via the given callback, but that seems to not be possible yet. + appendOnNewBlocks(assets: assets.dropFirst()) + } + + /// Adds the given images to the requesting block + /// - Parameter assets: Tenor Media objects to add. + func insertOnBlock(with assets: [TenorMedia]) { + guard let callback = mediaPickerCallback else { + return assertionFailure("Image picked without callback") + } + + let mediaInfo = assets.compactMap { (asset) -> MediaInfo? in + guard let media = self.mediaInserter.insert(exportableAsset: asset, source: .giphy) else { + return nil + } + let mediaUploadID = media.gutenbergUploadID + return MediaInfo(id: mediaUploadID, url: asset.URL.absoluteString, type: media.mediaTypeString) + } + + callback(mediaInfo) + } + + /// Create a new image block for each of the image objects in the slice. + /// - Parameter assets: Tenor Media objects to append. + func appendOnNewBlocks(assets: ArraySlice) { + assets.forEach { + if let media = self.mediaInserter.insert(exportableAsset: $0, source: .giphy) { + self.gutenberg.appendMedia(id: media.gutenbergUploadID, url: $0.URL, type: .image) + } + } + } +} diff --git a/WordPress/Classes/ViewRelated/Media/Tenor/TenorPicker.swift b/WordPress/Classes/ViewRelated/Media/Tenor/TenorPicker.swift index df47d866dbf6..97de3be57d81 100644 --- a/WordPress/Classes/ViewRelated/Media/Tenor/TenorPicker.swift +++ b/WordPress/Classes/ViewRelated/Media/Tenor/TenorPicker.swift @@ -7,6 +7,16 @@ protocol TenorPickerDelegate: AnyObject { /// Presents the Tenor main interface final class TenorPicker: NSObject { + // MARK: - Public properties + + var allowMultipleSelection = true { + didSet { + pickerOptions.allowMultipleSelection = allowMultipleSelection + } + } + + // MARK: - Private properties + private lazy var dataSource: TenorDataSource = { TenorDataSource(service: tenorService) }() @@ -25,7 +35,7 @@ final class TenorPicker: NSObject { private let searchHint = NoResultsViewController.controller() - private var pickerOptions: WPMediaPickerOptions = { + private lazy var pickerOptions: WPMediaPickerOptions = { let options = WPMediaPickerOptions() options.showMostRecentFirst = true options.filter = [.all] @@ -33,6 +43,7 @@ final class TenorPicker: NSObject { options.showSearchBar = true options.badgedUTTypes = [String(kUTTypeGIF)] options.preferredStatusBarStyle = .lightContent + options.allowMultipleSelection = allowMultipleSelection return options }() diff --git a/WordPress/WordPress.xcodeproj/project.pbxproj b/WordPress/WordPress.xcodeproj/project.pbxproj index 9abf5b054a44..e171d72e8c92 100644 --- a/WordPress/WordPress.xcodeproj/project.pbxproj +++ b/WordPress/WordPress.xcodeproj/project.pbxproj @@ -1579,6 +1579,7 @@ C8567492243F3751001A995E /* tenor-search-response.json in Resources */ = {isa = PBXBuildFile; fileRef = C8567491243F3751001A995E /* tenor-search-response.json */; }; C8567494243F388F001A995E /* tenor-invalid-search-reponse.json in Resources */ = {isa = PBXBuildFile; fileRef = C8567493243F388F001A995E /* tenor-invalid-search-reponse.json */; }; C8567496243F3D37001A995E /* TenorResultsPageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8567495243F3D37001A995E /* TenorResultsPageTests.swift */; }; + C856748F243EF177001A995E /* GutenbergTenorMediaPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = C856748E243EF177001A995E /* GutenbergTenorMediaPicker.swift */; }; C8567498243F41CA001A995E /* MockTenorService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8567497243F41CA001A995E /* MockTenorService.swift */; }; C856749A243F4292001A995E /* TenorMockDataHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C8567499243F4292001A995E /* TenorMockDataHelper.swift */; }; CC19BE06223FECAC00CAB3E1 /* EditorPostSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC19BE05223FECAC00CAB3E1 /* EditorPostSettings.swift */; }; @@ -4047,6 +4048,7 @@ C8567491243F3751001A995E /* tenor-search-response.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "tenor-search-response.json"; sourceTree = ""; }; C8567493243F388F001A995E /* tenor-invalid-search-reponse.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "tenor-invalid-search-reponse.json"; sourceTree = ""; }; C8567495243F3D37001A995E /* TenorResultsPageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TenorResultsPageTests.swift; sourceTree = ""; }; + C856748E243EF177001A995E /* GutenbergTenorMediaPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GutenbergTenorMediaPicker.swift; sourceTree = ""; }; C8567497243F41CA001A995E /* MockTenorService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTenorService.swift; sourceTree = ""; }; C8567499243F4292001A995E /* TenorMockDataHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TenorMockDataHelper.swift; sourceTree = ""; }; C856749B243F462F001A995E /* TenorDataSouceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TenorDataSouceTests.swift; sourceTree = ""; }; @@ -7105,6 +7107,7 @@ 8B05D29223AA572A0063B9AA /* GutenbergMediaEditorImage.swift */, 7E407120237163B8003627FA /* GutenbergStockPhotos.swift */, 7E4071392372AD54003627FA /* GutenbergFilesAppMediaSource.swift */, + C856748E243EF177001A995E /* GutenbergTenorMediaPicker.swift */, ); path = Utils; sourceTree = ""; @@ -11957,6 +11960,7 @@ E66969DC1B9E55C300EC9C00 /* ReaderTopicToReaderListTopic37to38.swift in Sources */, 31C9F82E1A2368A2008BB945 /* BlogDetailHeaderView.m in Sources */, 9A2D0B23225CB92B009E585F /* BlogService+JetpackConvenience.swift in Sources */, + C856748F243EF177001A995E /* GutenbergTenorMediaPicker.swift in Sources */, B50C0C5F1EF42A4A00372C65 /* AztecPostViewController.swift in Sources */, FF28B3F11AEB251200E11AAE /* InfoPListTranslator.m in Sources */, 086E1FE01BBB35D2002D86CA /* MenusViewController.m in Sources */,