Files
projet_gestion_commande/frontend-admin/node_modules/expo-image-picker/ios/ImagePickerModule.swift
T
2026-02-07 22:33:02 +01:00

252 lines
9.3 KiB
Swift

// Copyright 2022-present 650 Industries. All rights reserved.
import UIKit
import PhotosUI
import ExpoModulesCore
typealias MediaInfo = [UIImagePickerController.InfoKey: Any]
/**
Helper struct storing single picking operation context variables that have their own non-sharable state.
*/
struct PickingContext {
let promise: Promise
let options: ImagePickerOptions
let imagePickerHandler: ImagePickerHandler
}
enum OperationType {
case ask
case get
}
public class ImagePickerModule: Module, OnMediaPickingResultHandler {
public func definition() -> ModuleDefinition {
// TODO: (@bbarthec) change to "ExpoImagePicker" and propagate to other platforms
Name("ExponentImagePicker")
OnCreate {
self.appContext?.permissions?.register([
CameraPermissionRequester(),
MediaLibraryPermissionRequester(),
MediaLibraryWriteOnlyPermissionRequester()
])
}
AsyncFunction("getCameraPermissionsAsync", { (promise: Promise) in
self.handlePermissionRequest(requesterClass: CameraPermissionRequester.self, operationType: .get, promise: promise)
})
AsyncFunction("getMediaLibraryPermissionsAsync", { (writeOnly: Bool, promise: Promise) in
self.handlePermissionRequest(requesterClass: self.getMediaLibraryPermissionRequester(writeOnly), operationType: .get, promise: promise)
})
AsyncFunction("requestCameraPermissionsAsync", { (promise: Promise) in
self.handlePermissionRequest(requesterClass: CameraPermissionRequester.self, operationType: .ask, promise: promise)
})
AsyncFunction("requestMediaLibraryPermissionsAsync", { (writeOnly: Bool, promise: Promise) in
self.handlePermissionRequest(requesterClass: self.getMediaLibraryPermissionRequester(writeOnly), operationType: .ask, promise: promise)
})
AsyncFunction("launchCameraAsync", { (options: ImagePickerOptions, promise: Promise) in
guard let permissions = self.appContext?.permissions else {
return promise.reject(PermissionsModuleNotFoundException())
}
guard permissions.hasGrantedPermission(usingRequesterClass: CameraPermissionRequester.self) else {
return promise.reject(MissingCameraPermissionException())
}
self.launchImagePicker(sourceType: .camera, options: options, promise: promise)
})
.runOnQueue(DispatchQueue.main)
AsyncFunction("launchImageLibraryAsync", { (options: ImagePickerOptions, promise: Promise) in
self.launchImagePicker(sourceType: .photoLibrary, options: options, promise: promise)
})
.runOnQueue(DispatchQueue.main)
}
private var currentPickingContext: PickingContext?
private func handlePermissionRequest(requesterClass: AnyClass, operationType: OperationType, promise: Promise) {
guard let permissions = self.appContext?.permissions else {
return promise.reject(PermissionsModuleNotFoundException())
}
switch operationType {
case .get: permissions.getPermissionUsingRequesterClass(requesterClass, resolve: promise.resolver, reject: promise.legacyRejecter)
case .ask: permissions.askForPermission(usingRequesterClass: requesterClass, resolve: promise.resolver, reject: promise.legacyRejecter)
}
}
private func getMediaLibraryPermissionRequester(_ writeOnly: Bool) -> AnyClass {
return writeOnly ? MediaLibraryWriteOnlyPermissionRequester.self : MediaLibraryPermissionRequester.self
}
private func launchImagePicker(sourceType: UIImagePickerController.SourceType, options: ImagePickerOptions, promise: Promise) {
let imagePickerDelegate = ImagePickerHandler(onMediaPickingResultHandler: self, hideStatusBarWhenPresented: options.allowsEditing && !options.allowsMultipleSelection)
let pickingContext = PickingContext(promise: promise,
options: options,
imagePickerHandler: imagePickerDelegate)
if !options.allowsEditing && sourceType != .camera {
self.launchMultiSelectPicker(pickingContext: pickingContext)
} else {
self.launchLegacyImagePicker(sourceType: sourceType, pickingContext: pickingContext)
}
}
private func launchLegacyImagePicker(sourceType: UIImagePickerController.SourceType, pickingContext: PickingContext) {
let options = pickingContext.options
let picker = UIImagePickerController()
picker.fixCannotMoveEditingBox()
if sourceType == .camera {
#if targetEnvironment(simulator)
return pickingContext.promise.reject(CameraUnavailableOnSimulatorException())
#else
picker.sourceType = .camera
picker.cameraDevice = options.cameraType == .front ? .front : .rear
#endif
}
if sourceType == .photoLibrary {
picker.sourceType = .photoLibrary
}
picker.mediaTypes = options.toMediaTypesArray()
if options.requiresMicrophonePermission() && sourceType == .camera {
do {
try checkMicrophonePermissions()
} catch {
pickingContext.promise.reject(error)
return
}
}
picker.videoExportPreset = options.videoExportPreset.toAVAssetExportPreset()
picker.videoQuality = options.videoQuality.toQualityType()
picker.videoMaximumDuration = options.videoMaxDuration
if options.allowsEditing {
picker.allowsEditing = options.allowsEditing
if options.videoMaxDuration > 600 {
return pickingContext.promise.reject(MaxDurationWhileEditingExceededException())
}
if options.videoMaxDuration == 0 {
picker.videoMaximumDuration = 600.0
}
}
presentPickerUI(picker, pickingContext: pickingContext)
}
private func checkMicrophonePermissions() throws {
guard Bundle.main.object(forInfoDictionaryKey: "NSMicrophoneUsageDescription") != nil else {
throw MissingMicrophonePermissionException()
}
}
private func launchMultiSelectPicker(pickingContext: PickingContext) {
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
let options = pickingContext.options
// selection limit = 1 --> single selection, reflects the old picker behavior
configuration.selectionLimit = options.allowsMultipleSelection ? options.selectionLimit : SINGLE_SELECTION
configuration.filter = options.toPickerFilter()
configuration.preferredAssetRepresentationMode = options.preferredAssetRepresentationMode.toAssetRepresentationMode()
configuration.selection = options.orderedSelection ? .ordered : .default
let picker = PHPickerViewController(configuration: configuration)
presentPickerUI(picker, pickingContext: pickingContext)
}
private func presentPickerUI(_ picker: PickerUIController, pickingContext context: PickingContext) {
guard let currentViewController = self.appContext?.utilities?.currentViewController() else {
return context.promise.reject(MissingCurrentViewControllerException())
}
picker.modalPresentationStyle = context.options.presentationStyle.toPresentationStyle()
if UIDevice.current.userInterfaceIdiom == .pad {
let viewFrame = currentViewController.view.frame
picker.popoverPresentationController?.sourceRect = CGRect(
x: viewFrame.midX,
y: viewFrame.maxY,
width: 0,
height: 0
)
picker.popoverPresentationController?.sourceView = currentViewController.view
}
picker.setResultHandler(context.imagePickerHandler)
// Store picking context as we're navigating to the different view controller (starting asynchronous flow)
self.currentPickingContext = context
currentViewController.present(picker, animated: true, completion: nil)
}
// MARK: - OnMediaPickingResultHandler
func didCancelPicking() {
self.currentPickingContext?.promise.resolve(ImagePickerResponse(assets: nil, canceled: true))
self.currentPickingContext = nil
}
func didPickMultipleMedia(selection: [PHPickerResult]) {
guard let options = self.currentPickingContext?.options,
let promise = self.currentPickingContext?.promise else {
log.error("Picking operation context has been lost.")
return
}
guard let fileSystem = self.appContext?.fileSystem else {
return promise.reject(FileSystemModuleNotFoundException())
}
let mediaHandler = MediaHandler(fileSystem: fileSystem,
options: options)
// Clean up the currently stored picking context
self.currentPickingContext = nil
Task {
do {
let assets = try await mediaHandler.handleMultipleMedia(selection)
promise.resolve(ImagePickerResponse(assets: assets, canceled: false))
} catch {
promise.reject(error)
}
}
}
func didPickMedia(mediaInfo: MediaInfo) {
guard let options = self.currentPickingContext?.options,
let promise = self.currentPickingContext?.promise else {
log.error("Picking operation context has been lost.")
return
}
guard let fileSystem = self.appContext?.fileSystem else {
return promise.reject(FileSystemModuleNotFoundException())
}
// Clean up the currently stored picking context
self.currentPickingContext = nil
let mediaHandler = MediaHandler(fileSystem: fileSystem,
options: options)
Task {
do {
let asset = try await mediaHandler.handleMedia(mediaInfo)
promise.resolve(ImagePickerResponse(assets: [asset], canceled: false))
} catch {
promise.reject(error)
}
}
}
}