/* Copyright 2019-2024 New Vector Ltd. SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ import Foundation import UIKit import AVFoundation import PhotosUI @objc protocol SingleImagePickerPresenterDelegate: AnyObject { func singleImagePickerPresenter(_ presenter: SingleImagePickerPresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) func singleImagePickerPresenterDidCancel(_ presenter: SingleImagePickerPresenter) func singleImagePickerPresenterDidRemoveImage(_ presenter: SingleImagePickerPresenter) } /// SingleImagePickerPresenter enables to present an image picker with single selection @objcMembers final class SingleImagePickerPresenter: NSObject { // MARK: - Constants private enum Constants { static let jpegCompressionQuality: CGFloat = 1.0 } // MARK: - Properties // MARK: Private private let session: MXSession private weak var presentingViewController: UIViewController? private var cameraPresenter: CameraPresenter? private var mediaPickerPresenter: MediaPickerCoordinatorBridgePresenter? // MARK: Public weak var delegate: SingleImagePickerPresenterDelegate? var allowsRemoveImage = false // MARK: - Setup init(session: MXSession) { self.session = session } // MARK: - Public func present(from presentingViewController: UIViewController, sourceView: UIView?, sourceRect: CGRect, animated: Bool) { let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) let cameraAction = UIAlertAction(title: VectorL10n.imagePickerActionCamera, style: .default, handler: { _ in self.presentCamera(animated: animated) }) let photoLibraryAction = UIAlertAction(title: VectorL10n.imagePickerActionLibrary, style: .default, handler: { _ in self.presentPhotoLibray(sourceView: sourceView, sourceRect: sourceRect, animated: animated) }) let cancelAction = UIAlertAction(title: VectorL10n.cancel, style: .cancel) alert.addAction(cameraAction) alert.addAction(photoLibraryAction) // BWI: remove avatars if BWIBuildSettings.shared.enableRemoveAvatarImage && allowsRemoveImage { let removePhotoAction = UIAlertAction(title: BWIL10n.imagePickerActionRemovePhoto, style: .destructive, handler: { _ in self.removeImage() }) alert.addAction(removePhotoAction) } alert.addAction(cancelAction) if let popoverPresentationController = alert.popoverPresentationController { popoverPresentationController.sourceView = sourceView popoverPresentationController.sourceRect = sourceRect } presentingViewController.present(alert, animated: animated, completion: nil) self.presentingViewController = presentingViewController } func dismiss(animated: Bool, completion: (() -> Void)?) { if let cameraPresenter = self.cameraPresenter { cameraPresenter.dismiss(animated: animated, completion: completion) } else if let mediaPickerPresenter = self.mediaPickerPresenter { mediaPickerPresenter.dismiss(animated: animated, completion: completion) } } // MARK: - Private private func presentCamera(animated: Bool) { guard let presentingViewController = self.presentingViewController else { return } let cameraPresenter = CameraPresenter() cameraPresenter.delegate = self cameraPresenter.presentCamera(from: presentingViewController, with: [.image], animated: animated) self.cameraPresenter = cameraPresenter } private func presentPhotoLibray(sourceView: UIView?, sourceRect: CGRect, animated: Bool) { guard let presentingViewController = self.presentingViewController else { return } if BWIBuildSettings.shared.useNewPhotosPickerAPI { var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared()) configuration.filter = .images configuration.preferredAssetRepresentationMode = .compatible configuration.selection = .default configuration.selectionLimit = 1 configuration.preselectedAssetIdentifiers = [] let pickerViewController = PHPickerViewController(configuration: configuration) pickerViewController.delegate = self presentingViewController.present(pickerViewController, animated: true) } else { let mediaPickerPresenter = MediaPickerCoordinatorBridgePresenter(session: self.session, mediaUTIs: [.image], allowsMultipleSelection: false) mediaPickerPresenter.delegate = self mediaPickerPresenter.present(from: presentingViewController, sourceView: sourceView, sourceRect: sourceRect, animated: animated) self.mediaPickerPresenter = mediaPickerPresenter } } private func removeImage() { self.delegate?.singleImagePickerPresenterDidRemoveImage(self) } } // MARK: - CameraPresenterDelegate extension SingleImagePickerPresenter: CameraPresenterDelegate { func cameraPresenter(_ cameraPresenter: CameraPresenter, didSelectImage image: UIImage) { if let imageData = image.jpegData(compressionQuality: Constants.jpegCompressionQuality) { self.delegate?.singleImagePickerPresenter(self, didSelectImageData: imageData, withUTI: MXKUTI.jpeg) } } func cameraPresenterDidCancel(_ cameraPresenter: CameraPresenter) { self.delegate?.singleImagePickerPresenterDidCancel(self) } func cameraPresenter(_ cameraPresenter: CameraPresenter, didSelectVideoAt url: URL) { self.delegate?.singleImagePickerPresenterDidCancel(self) } } // MARK: - MediaPickerCoordinatorBridgePresenterDelegate extension SingleImagePickerPresenter: MediaPickerCoordinatorBridgePresenterDelegate { func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectImageData imageData: Data, withUTI uti: MXKUTI?) { self.delegate?.singleImagePickerPresenter(self, didSelectImageData: imageData, withUTI: uti) } func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectVideo videoAsset: AVAsset) { self.delegate?.singleImagePickerPresenterDidCancel(self) } func mediaPickerCoordinatorBridgePresenter(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter, didSelectAssets assets: [PHAsset]) { self.delegate?.singleImagePickerPresenterDidCancel(self) } func mediaPickerCoordinatorBridgePresenterDidCancel(_ coordinatorBridgePresenter: MediaPickerCoordinatorBridgePresenter) { self.delegate?.singleImagePickerPresenterDidCancel(self) } } // MARK: - PHPickerViewControllerDelegate (BWI) extension SingleImagePickerPresenter: PHPickerViewControllerDelegate { func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { picker.dismiss(animated: true) guard let itemProvider = results.first?.itemProvider else { self.delegate?.singleImagePickerPresenterDidCancel(self) return } // bwi: #5872 workaround for known issue (63426347) selection of images on iOS 15 (simulator) let heicId = "public.heic" if itemProvider.registeredTypeIdentifiers.contains(heicId) { itemProvider.loadDataRepresentation(forTypeIdentifier: heicId) { imageData, error in DispatchQueue.main.async { if let imageData = imageData, let image = UIImage(data: imageData), let imageData = image.pngData() { let uti = MXKUTI(mimeType: "image/png") self.delegate?.singleImagePickerPresenter(self, didSelectImageData: imageData, withUTI: uti) } else { self.delegate?.singleImagePickerPresenterDidCancel(self) } } } } else { if itemProvider.canLoadObject(ofClass: UIImage.self) { itemProvider.loadObject(ofClass: UIImage.self) { image, error in DispatchQueue.main.async { if let image = image as? UIImage, let imageData = image.pngData() { let uti = MXKUTI(mimeType: "image/png") self.delegate?.singleImagePickerPresenter(self, didSelectImageData: imageData, withUTI: uti) } else { self.delegate?.singleImagePickerPresenterDidCancel(self) } } } } else { self.delegate?.singleImagePickerPresenterDidCancel(self) } } } }