diff --git a/Modules/Package.swift b/Modules/Package.swift index ff2bad209ad0..4b5494e85591 100644 --- a/Modules/Package.swift +++ b/Modules/Package.swift @@ -47,7 +47,7 @@ let package = Package( .package(url: "https://github.com/zendesk/support_sdk_ios", from: "8.0.3"), // We can't use wordpress-rs branches nor commits here. Only tags work. .package(url: "https://github.com/Automattic/wordpress-rs", revision: "alpha-swift-20240813"), - .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "849118af582068f75807bc0f1265edeee4bf1b5e"), + .package(url: "https://github.com/wordpress-mobile/GutenbergKit", revision: "6cc307e7fc24910697be5f71b7d70f465a9c0f63"), .package(url: "https://github.com/Automattic/color-studio", branch: "trunk"), ], targets: XcodeSupport.targets + [ diff --git a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved index 79f583b50b23..6e5903f96a77 100644 --- a/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/WordPress.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -149,7 +149,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/wordpress-mobile/GutenbergKit", "state" : { - "revision" : "849118af582068f75807bc0f1265edeee4bf1b5e" + "revision" : "6cc307e7fc24910697be5f71b7d70f465a9c0f63" } }, { diff --git a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaPickerHelper.swift b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaPickerHelper.swift index f3622b3563d6..d1b7e1cb93ac 100644 --- a/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaPickerHelper.swift +++ b/WordPress/Classes/ViewRelated/Gutenberg/GutenbergMediaPickerHelper.swift @@ -38,12 +38,37 @@ final class GutenbergMediaPickerHelper: NSObject { context.present(picker, animated: true) } - func presentSiteMediaPicker(filter: WPMediaType, allowMultipleSelection: Bool, completion: @escaping GutenbergMediaPickerHelperCallback) { + func presentSiteMediaPicker(filter: WPMediaType, allowMultipleSelection: Bool, initialSelection: [Int] = [], completion: @escaping GutenbergMediaPickerHelperCallback) { didPickMediaCallback = completion - MediaPickerMenu(viewController: context, filter: .init(filter), isMultipleSelectionEnabled: allowMultipleSelection) + let initialMediaSelection = mapMediaIdsToMedia(initialSelection) + MediaPickerMenu(viewController: context, filter: .init(filter), isMultipleSelectionEnabled: allowMultipleSelection, initialSelection: initialMediaSelection) .showSiteMediaPicker(blog: post.blog, delegate: self) } + private func mapMediaIdsToMedia(_ mediaIds: [Int]) -> [Media] { + assert(Thread.isMainThread, "mapMediaIdsToMedia should only be called on the main thread") + let context = ContextManager.shared.mainContext + let request = NSFetchRequest(entityName: "Media") + request.predicate = NSPredicate(format: "mediaID IN %@", mediaIds.map { NSNumber(value: $0) }) + + do { + let fetchedMedia = try context.fetch(request) as? [Media] ?? [] + + // Create a dictionary for quick lookup + let mediaDict = Dictionary(uniqueKeysWithValues: fetchedMedia.compactMap { media -> (Int, Media)? in + if let mediaID = media.mediaID?.intValue { + return (mediaID, media) + } + return nil + }) + + // Map the original mediaIds to Media objects, preserving order + return mediaIds.compactMap { mediaDict[$0] } + } catch { + return [] + } + } + func presentCameraCaptureFullScreen(animated: Bool, filter: WPMediaType, callback: @escaping GutenbergMediaPickerHelperCallback) { diff --git a/WordPress/Classes/ViewRelated/Media/MediaPickerMenu.swift b/WordPress/Classes/ViewRelated/Media/MediaPickerMenu.swift index 42b07127670f..9f32289ffd59 100644 --- a/WordPress/Classes/ViewRelated/Media/MediaPickerMenu.swift +++ b/WordPress/Classes/ViewRelated/Media/MediaPickerMenu.swift @@ -9,6 +9,7 @@ struct MediaPickerMenu { weak var presentingViewController: UIViewController? var filter: MediaFilter? var isMultipleSelectionEnabled: Bool + var initialSelection: [Media] enum MediaFilter { case images @@ -21,12 +22,15 @@ struct MediaPickerMenu { /// - viewController: The view controller to use for presentation. /// - filter: By default, `nil` – allow all content types. /// - isMultipleSelectionEnabled: By default, `false`. + /// - initialSelection: By default, `[]`. init(viewController: UIViewController, filter: MediaFilter? = nil, - isMultipleSelectionEnabled: Bool = false) { + isMultipleSelectionEnabled: Bool = false, + initialSelection: [Media] = []) { self.presentingViewController = viewController self.filter = filter self.isMultipleSelectionEnabled = isMultipleSelectionEnabled + self.initialSelection = initialSelection } } @@ -185,7 +189,8 @@ extension MediaPickerMenu { let viewController = SiteMediaPickerViewController( blog: blog, filter: filter.map { [$0.mediaType] }, - allowsMultipleSelection: isMultipleSelectionEnabled + allowsMultipleSelection: isMultipleSelectionEnabled, + initialSelection: initialSelection ) viewController.delegate = delegate let navigation = UINavigationController(rootViewController: viewController) diff --git a/WordPress/Classes/ViewRelated/Media/SiteMedia/Controllers/SiteMediaCollectionViewController.swift b/WordPress/Classes/ViewRelated/Media/SiteMedia/Controllers/SiteMediaCollectionViewController.swift index 479e3b8558e3..34ff667395e3 100644 --- a/WordPress/Classes/ViewRelated/Media/SiteMedia/Controllers/SiteMediaCollectionViewController.swift +++ b/WordPress/Classes/ViewRelated/Media/SiteMedia/Controllers/SiteMediaCollectionViewController.swift @@ -73,6 +73,14 @@ final class SiteMediaCollectionViewController: UIViewController, NSFetchedResult fatalError("init(coder:) has not been implemented") } + private func setInitialSelection(_ media: [Media]) { + updateSelection { + for item in media { + selection.add(item) + } + } + } + func embed(in parentViewController: UIViewController) { parentViewController.addChild(self) parentViewController.view.addSubview(view) @@ -159,14 +167,19 @@ final class SiteMediaCollectionViewController: UIViewController, NSFetchedResult func setEditing( _ isEditing: Bool, allowsMultipleSelection: Bool = true, - isSelectionOrdered: Bool = false + isSelectionOrdered: Bool = false, + initialSelection: [Media]? = nil ) { guard self.isEditing != isEditing else { return } self.isEditing = isEditing self.allowsMultipleSelection = allowsMultipleSelection self.isSelectionOrdered = isSelectionOrdered - deselectAll() + if let selectedMedia = initialSelection, allowsMultipleSelection { + setInitialSelection(selectedMedia) + } else { + deselectAll() + } } private func updateSelection(_ perform: () -> Void) { diff --git a/WordPress/Classes/ViewRelated/Media/SiteMedia/SiteMediaPickerViewController.swift b/WordPress/Classes/ViewRelated/Media/SiteMedia/SiteMediaPickerViewController.swift index 95bd47d5b6da..169dd9f0b599 100644 --- a/WordPress/Classes/ViewRelated/Media/SiteMedia/SiteMediaPickerViewController.swift +++ b/WordPress/Classes/ViewRelated/Media/SiteMedia/SiteMediaPickerViewController.swift @@ -9,6 +9,7 @@ protocol SiteMediaPickerViewControllerDelegate: AnyObject { final class SiteMediaPickerViewController: UIViewController, SiteMediaCollectionViewControllerDelegate { private let blog: Blog private let allowsMultipleSelection: Bool + private let initialSelection: [Media] private let collectionViewController: SiteMediaCollectionViewController private let toolbarItemTitle = SiteMediaSelectionTitleView() @@ -22,9 +23,11 @@ final class SiteMediaPickerViewController: UIViewController, SiteMediaCollection /// - blog: The site that contains the media /// - filter: The types of media to display. By default, `nil` (show everything). /// - allowsMultipleSelection: `false` by default. - init(blog: Blog, filter: Set? = nil, allowsMultipleSelection: Bool = false) { + /// - initialSelection: `[]` by default. + init(blog: Blog, filter: Set? = nil, allowsMultipleSelection: Bool = false, initialSelection: [Media] = []) { self.blog = blog self.allowsMultipleSelection = allowsMultipleSelection + self.initialSelection = initialSelection self.collectionViewController = SiteMediaCollectionViewController(blog: blog, filter: filter, isShowingPendingUploads: false) super.init(nibName: nil, bundle: nil) @@ -65,7 +68,7 @@ final class SiteMediaPickerViewController: UIViewController, SiteMediaCollection // MARK: - Actions private func buttonCancelTapped() { - delegate?.siteMediaPickerViewController(self, didFinishWithSelection: []) + delegate?.siteMediaPickerViewController(self, didFinishWithSelection: initialSelection) } @objc private func buttonDoneTapped() { @@ -75,7 +78,7 @@ final class SiteMediaPickerViewController: UIViewController, SiteMediaCollection // MARK: - Selection private func startSelection() { - collectionViewController.setEditing(true, allowsMultipleSelection: allowsMultipleSelection, isSelectionOrdered: true) + collectionViewController.setEditing(true, allowsMultipleSelection: allowsMultipleSelection, isSelectionOrdered: true, initialSelection: initialSelection) if allowsMultipleSelection, toolbarItems == nil { var toolbarItems: [UIBarButtonItem] = [] diff --git a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift index e064a4605250..e55b31d38e84 100644 --- a/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift +++ b/WordPress/Classes/ViewRelated/NewGutenberg/NewGutenbergViewController.swift @@ -21,6 +21,10 @@ class NewGutenbergViewController: UIViewController, PostEditor, PublishingEditor SupportCoordinator(controllerToShowFrom: topmostPresentedViewController, tag: .editorHelp) }() + lazy var mediaPickerHelper: GutenbergMediaPickerHelper = { + return GutenbergMediaPickerHelper(context: self, post: post) + }() + // MARK: - PostEditor private(set) lazy var postEditorStateContext: PostEditorStateContext = { @@ -335,6 +339,73 @@ extension NewGutenbergViewController: GutenbergKit.EditorViewControllerDelegate func editor(_ viewController: GutenbergKit.EditorViewController, performRequest: GutenbergKit.EditorNetworkRequest) async throws -> GutenbergKit.EditorNetworkResponse { throw URLError(.unknown) } + + func editor(_ viewController: GutenbergKit.EditorViewController, didRequestMediaFromSiteMediaLibrary config: OpenMediaLibraryAction) { + let flags = mediaFilterFlags(using: config.allowedTypes ?? []) + + let initialSelectionArray: [Int] + switch config.value { + case .single(let id): + initialSelectionArray = [id] + case .multiple(let ids): + initialSelectionArray = ids + case .none: + initialSelectionArray = [] + } + + mediaPickerHelper.presentSiteMediaPicker(filter: flags, allowMultipleSelection: config.multiple, initialSelection: initialSelectionArray) { [weak self] assets in + guard let self, let media = assets as? [Media] else { + self?.editorViewController.setMediaUploadAttachment("[]") + return + } + let mediaInfos = media.map { item in + var metadata: [String: String] = [:] + if let videopressGUID = item.videopressGUID { + metadata["videopressGUID"] = videopressGUID + } + return MediaInfo(id: item.mediaID?.int32Value, url: item.remoteURL, type: item.mediaTypeString, caption: item.caption, title: item.filename, alt: item.alt, metadata: [:]) + } + if let jsonString = convertMediaInfoArrayToJSONString(mediaInfos) { + // Escape the string for JavaScript + let escapedJsonString = jsonString.replacingOccurrences(of: "'", with: "\\'") + editorViewController.setMediaUploadAttachment(escapedJsonString) + } + } + } + + private func convertMediaInfoArrayToJSONString(_ mediaInfoArray: [MediaInfo]) -> String? { + do { + let jsonData = try JSONEncoder().encode(mediaInfoArray) + if let jsonString = String(data: jsonData, encoding: .utf8) { + return jsonString + } + } catch { + print("Error encoding MediaInfo array: \(error)") + } + return nil + } + + private func mediaFilterFlags(using filterArray: [OpenMediaLibraryAction.MediaType]) -> WPMediaType { + var mediaType: Int = 0 + for filter in filterArray { + switch filter { + case .image: + mediaType = mediaType | WPMediaType.image.rawValue + case .video: + mediaType = mediaType | WPMediaType.video.rawValue + case .audio: + mediaType = mediaType | WPMediaType.audio.rawValue + case .other: + mediaType = mediaType | WPMediaType.other.rawValue + case .any: + mediaType = mediaType | WPMediaType.all.rawValue + @unknown default: + fatalError() + } + } + + return WPMediaType(rawValue: mediaType) + } } private struct NewGutenbergNetworkClient: GutenbergKit.EditorNetworkingClient {