one
Version:
One is a React framework for web, iOS, and Android apps.
86 lines (76 loc) • 3.43 kB
text/typescript
import { NitroModules } from 'react-native-nitro-modules'
import type { ImagePickerNativeResult, OneImagePicker } from '../specs/OneImagePicker.nitro'
import { rethrowNativeError } from '../nativeError'
import { resolveCameraOptions, resolveImagePickerOptions } from './options'
import type {
ImagePickerOptions,
ImagePickerPermissionResponse,
ImagePickerResult,
} from './types'
export type * from './types'
// the OneImagePicker nitro hybrid object is created once and lazily. null
// outside a native build, exactly like the other native modules in
// this package.
let hybrid: OneImagePicker | null | undefined
function native(verb: string): OneImagePicker {
if (hybrid === undefined) {
hybrid = NitroModules.hasHybridObject('OneImagePicker')
? NitroModules.createHybridObject<OneImagePicker>('OneImagePicker')
: null
}
if (!hybrid) {
throw new Error(
`ImagePicker.${verb} needs a native build`
)
}
return hybrid
}
// native settles one flat result; assets are set only when not canceled.
function toResult(result: ImagePickerNativeResult): ImagePickerResult {
if (result.canceled) return { canceled: true, assets: null }
if (!result.assets) {
throw new Error('ImagePicker: native returned a pick without assets')
}
return { canceled: false, assets: result.assets }
}
// present the system photo picker. ios uses PHPickerViewController, which
// needs no permission prompt; android uses the system photo picker with a
// documents fallback on devices without it. picked assets are copied into
// the app cache and returned as file uris. backing out resolves
// { canceled: true, assets: null }. plain, not async, so bad options throw
// synchronously; the one-in-flight slot lives in native.
function launchLibrary(
options: ImagePickerOptions = {}
): Promise<ImagePickerResult> {
const resolved = resolveImagePickerOptions(options)
return native('launchLibrary').launchLibrary(resolved).then(toResult, rethrowNativeError)
}
// capture one asset with the system camera: a still photo unless mediaTypes
// is videos-only, which records one video. a denied permission, a missing
// camera, and backing out all resolve { canceled: true, assets: null };
// check getCameraPermissions first when the distinction matters. needs the
// camera permission declared through native.app imagePicker and rerun
// through one prebuild; without it the call rejects. camera video on ios
// also needs the microphone string (native.app imagePicker.microphone),
// since the recording carries audio.
function launchCamera(
options: ImagePickerOptions = {}
): Promise<ImagePickerResult> {
const resolved = resolveCameraOptions(options)
return native('launchCamera').launchCamera(resolved).then(toResult, rethrowNativeError)
}
// read the camera permission without prompting. outside the native pending
// slot, so it answers during a pick.
async function getCameraPermissions(): Promise<ImagePickerPermissionResponse> {
return native('getCameraPermissions').getCameraPermissions()
}
// prompt for the camera permission unless it is already decided.
async function requestCameraPermissions(): Promise<ImagePickerPermissionResponse> {
return native('requestCameraPermissions').requestCameraPermissions().catch(rethrowNativeError)
}
export const ImagePicker = Object.freeze({
launchLibrary,
launchCamera,
getCameraPermissions,
requestCameraPermissions,
})