diff --git a/MultipleImagePicker.podspec b/MultipleImagePicker.podspec index 8d3c3b06..23420213 100644 --- a/MultipleImagePicker.podspec +++ b/MultipleImagePicker.podspec @@ -28,7 +28,8 @@ Pod::Spec.new do |s| s.dependency "HXPhotoPicker/Picker", "4.2.4" - s.dependency "HXPhotoPicker/Editor/Lite", "4.2.4" + s.dependency "HXPhotoPicker/Camera/Lite", "4.2.4" + s.dependency "HXPhotoPicker/Editor", "4.2.4" s.pod_target_xcconfig = { # C++ compiler flags, mainly for folly. diff --git a/README.md b/README.md index ca420fb6..ff532bc1 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ React Native Multiple Image Picker **(RNMIP)** enables application to pick image | 📺 | Display video duration. | | 🎆 | Preview image/video. | | ⛅️ | Support iCloud Photo Library. | -| 🔪 | Crop single/multiple image (new) ✨ | +| 🍕 | Crop single/multiple image (new) ✨ | | 🌪 | Scrolling performance. ☕️ | ## Installation diff --git a/android/src/main/java/com/margelo/nitro/multipleimagepicker/CameraEngine.kt b/android/src/main/java/com/margelo/nitro/multipleimagepicker/CameraEngine.kt index af41cdb6..282bdccd 100644 --- a/android/src/main/java/com/margelo/nitro/multipleimagepicker/CameraEngine.kt +++ b/android/src/main/java/com/margelo/nitro/multipleimagepicker/CameraEngine.kt @@ -3,26 +3,37 @@ package com.margelo.nitro.multipleimagepicker import android.content.Context import androidx.fragment.app.Fragment import com.bumptech.glide.Glide +import com.facebook.react.bridge.ColorPropConverter import com.luck.lib.camerax.SimpleCameraX import com.luck.picture.lib.interfaces.OnCameraInterceptListener import java.io.File -class CameraEngine(private val appContext: Context, val config: PickerCameraConfig) : OnCameraInterceptListener { +class CameraEngine( + private val appContext: Context, + val config: NitroCameraConfig, +) : + OnCameraInterceptListener { override fun openCamera(fragment: Fragment, cameraMode: Int, requestCode: Int) { val camera = SimpleCameraX.of() + + camera.setImageEngine { context, url, imageView -> + Glide.with(context).load(url).into(imageView) + } + camera.isAutoRotation(true) camera.setCameraMode(cameraMode) camera.isDisplayRecordChangeTime(true) camera.isManualFocusCameraPreview(true) camera.isZoomCameraPreview(true) - camera.setOutputPathDir(getSandboxCameraOutputPath()) camera.setRecordVideoMaxSecond(config.videoMaximumDuration?.toInt() ?: 60) + camera.setCameraAroundState(config.cameraDevice == CameraDevice.FRONT) + camera.setOutputPathDir(getSandboxCameraOutputPath()) -// camera.setPermissionDeniedListener(getSimpleXPermissionDeniedListener()) -// camera.setPermissionDescriptionListener(getSimpleXPermissionDescriptionListener()) - camera.setImageEngine { context, url, imageView -> - Glide.with(context).load(url).into(imageView) + config.color?.let { + val primaryColor = ColorPropConverter.getColor(it, appContext) + camera.setCaptureLoadingColor(primaryColor) } + camera.start(fragment.requireActivity(), fragment, requestCode) } diff --git a/android/src/main/java/com/margelo/nitro/multipleimagepicker/MultipleImagePicker.kt b/android/src/main/java/com/margelo/nitro/multipleimagepicker/MultipleImagePicker.kt index fcd39e52..dd871c06 100644 --- a/android/src/main/java/com/margelo/nitro/multipleimagepicker/MultipleImagePicker.kt +++ b/android/src/main/java/com/margelo/nitro/multipleimagepicker/MultipleImagePicker.kt @@ -34,4 +34,12 @@ class MultipleImagePicker : HybridMultipleImagePickerSpec() { pickerModule.openPreview(media, index.toInt(), config) } + override fun openCamera( + config: NitroCameraConfig, + resolved: (result: CameraResult) -> Unit, + rejected: (reject: Double) -> Unit + ) { + pickerModule.openCamera(config, resolved, rejected) + } + } \ No newline at end of file diff --git a/android/src/main/java/com/margelo/nitro/multipleimagepicker/MultipleImagePickerImp.kt b/android/src/main/java/com/margelo/nitro/multipleimagepicker/MultipleImagePickerImp.kt index d391dc31..e3af2913 100644 --- a/android/src/main/java/com/margelo/nitro/multipleimagepicker/MultipleImagePickerImp.kt +++ b/android/src/main/java/com/margelo/nitro/multipleimagepicker/MultipleImagePickerImp.kt @@ -73,11 +73,7 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) : setStyle() // set style for UI handleSelectedAssets(config) - val chooseMode = when (config.mediaType) { - MediaType.VIDEO -> SelectMimeType.ofVideo() - MediaType.IMAGE -> SelectMimeType.ofImage() - else -> SelectMimeType.ofAll() - } + val chooseMode = getChooseMode(config.mediaType) val maxSelect = config.maxSelect?.toInt() ?: 20 val maxVideo = config.maxVideo?.toInt() ?: 20 @@ -96,7 +92,6 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) : .setImageEngine(imageEngine) .setSelectedData(dataList) .setSelectorUIStyle(style) - .apply { if (isCrop) { setCropOption(config.crop) @@ -116,13 +111,24 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) : setFilterMaxFileSize(it) } - isDisplayCamera(config.camera != null) config.camera?.let { - setCameraInterceptListener(CameraEngine(appContext, it)) + val cameraConfig = NitroCameraConfig( + mediaType = MediaType.ALL, + presentation = Presentation.FULLSCREENMODAL, + language = Language.SYSTEM, + crop = null, + isSaveSystemAlbum = false, + color = config.primaryColor, + cameraDevice = it.cameraDevice, + videoMaximumDuration = it.videoMaximumDuration + ) + + setCameraInterceptListener(CameraEngine(appContext, cameraConfig)) } } + .setVideoThumbnailListener(VideoThumbnailEngine(getVideoThumbnailDir())) .setImageSpanCount(config.numberOfColumn?.toInt() ?: 3) .setMaxSelectNum(maxSelect) .isDirectReturnSingle(true) @@ -301,7 +307,7 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) : .setLanguage(getLanguage(config.language)) .setSelectorUIStyle(previewStyle) .isPreviewFullScreenMode(true) - .isAutoVideoPlay(true) + .isAutoVideoPlay(config.videoAutoPlay == true) .setVideoPlayerEngine(ExoPlayerEngine()) .isVideoPauseResumePlay(true) .setCustomLoadingListener(getCustomLoadingListener()) @@ -312,6 +318,70 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) : return OnCustomLoadingListener { context -> LoadingDialog(context) } } + @ReactMethod + fun openCamera( + config: NitroCameraConfig, + resolved: (result: CameraResult) -> Unit, + rejected: (reject: Double) -> Unit + ) { + val activity = currentActivity + val chooseMode = getChooseMode(config.mediaType) + + PictureSelector + .create(activity) + .openCamera(chooseMode) + .setLanguage(getLanguage(config.language)) + .setCameraInterceptListener(CameraEngine(appContext, config)) + .isQuickCapture(true) + .isOriginalControl(true) + .setVideoThumbnailListener(VideoThumbnailEngine(getVideoThumbnailDir())) + .apply { + if (config.crop != null) { + setCropEngine(CropEngine(cropOption)) + } + } + .forResultActivity(object : OnResultCallbackListener { + override fun onResult(results: java.util.ArrayList?) { + results?.first()?.let { + val result = getResult(it) + + resolved( + CameraResult( + path = result.path, + type = result.type, + width = result.width, + height = result.height, + duration = result.duration, + thumbnail = result.thumbnail, + fileName = result.fileName + ) + ) + } + } + + override fun onCancel() { +// rejected(0.0) + } + }) + } + + private fun getChooseMode(mediaType: MediaType): Int { + return when (mediaType) { + MediaType.VIDEO -> SelectMimeType.ofVideo() + MediaType.IMAGE -> SelectMimeType.ofImage() + else -> SelectMimeType.ofAll() + } + } + + private fun getVideoThumbnailDir(): String { + val externalFilesDir: File? = appContext.getExternalFilesDir("") + val customFile = File(externalFilesDir?.absolutePath, "Thumbnail") + if (!customFile.exists()) { + customFile.mkdirs() + } + return customFile.absolutePath + File.separator + } + private fun getLanguage(language: Language): Int { return when (language) { @@ -511,19 +581,23 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) : if (item.mimeType.startsWith("video/")) ResultType.VIDEO else ResultType.IMAGE var path = item.path - var width: Double = item.width.toDouble() var height: Double = item.height.toDouble() + val thumbnail = item.videoThumbnailPath?.let { + if (!it.startsWith("file://")) "file://$it" else it + } + if (item.isCut) { path = "file://${item.cutPath}" width = item.cropImageWidth.toDouble() height = item.cropImageHeight.toDouble() } + if (!path.startsWith("file://") && !path.startsWith("content://") && type == ResultType.IMAGE) + path = "file://$path" + val media = Result( - path, - fileName = item.fileName, localIdentifier = item.id.toString(), width, height, @@ -533,10 +607,12 @@ class MultipleImagePickerImp(reactContext: ReactApplicationContext?) : realPath = item.realPath, parentFolderName = item.parentFolderName, creationDate = item.dateAddedTime.toDouble(), + crop = item.isCut, + path, type, - duration = item.duration.toDouble(), - thumbnail = item.videoThumbnailPath, - crop = item.isCut + fileName = item.fileName, + thumbnail = thumbnail, + duration = item.duration.toDouble() ) return media diff --git a/android/src/main/java/com/margelo/nitro/multipleimagepicker/VideoThumbnailEngine.kt b/android/src/main/java/com/margelo/nitro/multipleimagepicker/VideoThumbnailEngine.kt new file mode 100644 index 00000000..dd42108e --- /dev/null +++ b/android/src/main/java/com/margelo/nitro/multipleimagepicker/VideoThumbnailEngine.kt @@ -0,0 +1,52 @@ +package com.margelo.nitro.multipleimagepicker + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import com.bumptech.glide.Glide +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import com.luck.picture.lib.interfaces.OnKeyValueResultCallbackListener +import com.luck.picture.lib.interfaces.OnVideoThumbnailEventListener +import com.luck.picture.lib.utils.PictureFileUtils +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.FileOutputStream +import java.io.IOException + + +class VideoThumbnailEngine(private val targetPath: String) : OnVideoThumbnailEventListener { + override fun onVideoThumbnail( + context: Context, videoPath: String, call: OnKeyValueResultCallbackListener + ) { + Glide.with(context).asBitmap().sizeMultiplier(0.6f).load(videoPath) + .into(object : CustomTarget() { + override fun onResourceReady( + resource: Bitmap, transition: Transition? + ) { + val stream = ByteArrayOutputStream() + resource.compress(Bitmap.CompressFormat.JPEG, 60, stream) + var fos: FileOutputStream? = null + var result: String? = null + try { + val targetFile = + File(targetPath, "thumbnails_" + System.currentTimeMillis() + ".jpg") + fos = FileOutputStream(targetFile) + fos.write(stream.toByteArray()) + fos.flush() + result = targetFile.absolutePath + } catch (e: IOException) { + e.printStackTrace() + } finally { + PictureFileUtils.close(fos) + PictureFileUtils.close(stream) + } + call.onCallback(videoPath, result) + } + + override fun onLoadCleared(placeholder: Drawable?) { + call.onCallback(videoPath, "") + } + }) + } +} \ No newline at end of file diff --git a/docs/docs/CAMERA.mdx b/docs/docs/CAMERA.mdx new file mode 100644 index 00000000..d13aff19 --- /dev/null +++ b/docs/docs/CAMERA.mdx @@ -0,0 +1,269 @@ +--- +id: camera +title: Camera 📸 +sidebar_label: Camera 📸 +slug: /camera +--- + +import ReactPlayer from 'react-player' + +The camera module provides a simple interface for capturing photos and recording videos with customizable options. + + + +## Usage + +```typescript +import { openCamera } from '@baronha/react-native-multiple-image-picker' + +const open = async () => { + try { + const response = await openCamera({ + mediaType: 'all', + cameraDevice: 'back' + }) + console.log(response) + } catch (e) { + console.log(e) + } +} +``` + +## Configuration Options + +### `mediaType` +Specifies the type of media that can be captured. + +```typescript +mediaType?: 'video' | 'image' | 'all' +``` + +**Default:** `'all'` + +### `cameraDevice` +Selects which camera to use for capture. + +```typescript +cameraDevice?: 'front' | 'back' +``` + +**Default:** `'back'` + +### `videoMaximumDuration` +Sets the maximum duration for video recording in seconds. + +```typescript +videoMaximumDuration?: number +``` + +**Default:** No limit + +### `presentation` +Controls how the camera view is presented (iOS only). + +```typescript +presentation?: 'fullScreenModal' | 'formSheet' +``` + +**Default:** `'fullScreenModal'` + +### `language` +Sets the interface language. + +```typescript +language?: Language +``` + +**Supported values:** +- 🌐 `'system'`: System default +- 🇨🇳 `'zh-Hans'`: Simplified Chinese +- 🇹🇼 `'zh-Hant'`: Traditional Chinese +- 🇯🇵 `'ja'`: Japanese +- 🇰🇷 `'ko'`: Korean +- 🇬🇧 `'en'`: English +- 🇹🇭 `'th'`: Thai +- 🇮🇩 `'id'`: Indonesian +- 🇻🇳 `'vi'`: Vietnamese +- 🇷🇺 `'ru'`: Russian +- 🇩🇪 `'de'`: German +- 🇫🇷 `'fr'`: French +- 🇸🇦 `'ar'`: Arabic + +**Default:** `'system'` + +### `crop` +Enables and configures image cropping after capture. + +See details in [Crop Configuration](/config/#crop-) + +### `color` + +- **Type**: [**ColorValue**](https://reactnative.dev/docs/colors) +- **Default**: 🟦 `#2979ff` +- **Required**: No +- **Platform**: iOS, Android + +## Result Type + +The camera function returns a `CameraResult` object: + +```typescript +interface CameraResult { + /** + * Path to the captured media file + * - iOS: Starts with 'file://' + * - Android: Can start with 'file://' or 'content://' + */ + path: string + + /** + * Type of captured media + * - 'video': For video recordings + * - 'image': For photos + */ + type: 'video' | 'image' + + /** + * Width of the media in pixels + * For cropped images, this represents the final cropped width + */ + width: number + + /** + * Height of the media in pixels + * For cropped images, this represents the final cropped height + */ + height: number + + /** + * Duration of the video in seconds + * Only available when type is 'video' + * @platform ios, android + */ + duration: number + + /** + * Path to the video thumbnail image + * Only available when type is 'video' + * Format: 'file://path/to/thumbnail.jpg' + * @platform ios, android + */ + thumbnail?: string + + /** + * Original filename of the captured media + * Example: 'IMG_1234.JPG' or 'VID_5678.MP4' + */ + fileName: string +} +``` + +### Example Response + +#### Photo Capture +```typescript +{ + path: 'file:///var/mobile/Containers/.../IMG_0123.JPG', + type: 'image', + width: 3024, + height: 4032, + fileName: 'IMG_0123.JPG' +} +``` + +#### Video Recording +```typescript +{ + path: 'file:///var/mobile/Containers/.../VID_0124.MP4', + type: 'video', + width: 1920, + height: 1080, + duration: 15.6, + thumbnail: 'file:///var/mobile/Containers/.../VID_0124_thumb.JPG', + fileName: 'VID_0124.MP4' +} +``` + +### Notes + +- The `path` format may vary between iOS and Android. Always use the provided path as-is. +- Video thumbnails are automatically generated and provided in the `thumbnail` property. +- For cropped images, the `width` and `height` reflect the dimensions after cropping. +- The `duration` property is only available for video recordings and is measured in seconds. +- All file paths are provided with the appropriate prefix (`file://` or `content://`). + +## Examples + +### Photo Capture +```typescript +const result = await openCamera({ + mediaType: 'image', + cameraDevice: 'back' +}) +``` + +### Video Recording +```typescript +const result = await openCamera({ + mediaType: 'video', + videoMaximumDuration: 30, + cameraDevice: 'front' +}) +``` + +### With Cropping +```typescript +const result = await openCamera({ + mediaType: 'image', + crop: { + circle: true, + ratio: [ + { title: "Square", width: 1, height: 1 }, + { title: "Portrait", width: 3, height: 4 } + ] + } +}) +``` + +### Custom UI +```typescript +const result = await openCamera({ + color: '#FF6B6B', + language: 'en', + presentation: 'fullScreenModal' +}) +``` + +## Platform Specific Notes + +### iOS +- Supports `presentation` option for modal style +- Full support for all UI customization options + +### Android +- Maximum 4 custom crop ratios +- Some UI elements may appear differently + +## Required Permissions + +### iOS +Add to `Info.plist`: +```xml +NSCameraUsageDescription +Camera access is required to take photos and videos +NSMicrophoneUsageDescription +Microphone access is required to record videos +``` + +### Android +Add to `AndroidManifest.xml`: +```xml + + +``` diff --git a/docs/docs/CONFIG.mdx b/docs/docs/CONFIG.mdx index 00cf2bda..d23c991b 100644 --- a/docs/docs/CONFIG.mdx +++ b/docs/docs/CONFIG.mdx @@ -177,7 +177,7 @@ Theme mode for the picker. Primary color for the picker UI elements. -- **Type**: ColorValue +- **Type**: [**ColorValue**](https://reactnative.dev/docs/colors) - **Default**: 🟦 `#2979ff` - **Required**: No - **Platform**: iOS, Android @@ -186,7 +186,7 @@ Primary color for the picker UI elements. Background color for dark mode UI elements. -- **Type**: ColorValue +- **Type**: [**ColorValue**](https://reactnative.dev/docs/colors) - **Default**: ⚫️ `#1A1A1A` - **Required**: No - **Platform**: iOS, Android diff --git a/docs/docs/CROP.mdx b/docs/docs/CROP.mdx index 42ba98f6..87a559cb 100644 --- a/docs/docs/CROP.mdx +++ b/docs/docs/CROP.mdx @@ -1,7 +1,7 @@ --- id: crop -title: Open Crop -sidebar_label: Open Crop +title: Crop 🍕 +sidebar_label: Crop 🍕 slug: /crop --- diff --git a/docs/docs/PREVIEW.mdx b/docs/docs/PREVIEW.mdx index 2482ea14..d4b3511d 100644 --- a/docs/docs/PREVIEW.mdx +++ b/docs/docs/PREVIEW.mdx @@ -1,7 +1,7 @@ --- id: preview -title: Open Preview -sidebar_label: Open Preview +title: Preview 🎑 +sidebar_label: Preview 🎑 slug: /preview --- diff --git a/docs/docs/index.md b/docs/docs/index.md index a6561cc5..40e724d0 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -35,7 +35,7 @@ React Native Multiple Image Picker **(RNMIP)** enables application to pick image | 📺 | Display video duration. | | 🎆 | Preview image/video. | | ⛅️ | Support iCloud Photo Library. | -| 🔪 | Crop single/multiple image (new) ✨ | +| 🍕 | Crop single/multiple image (new) ✨ | | 🌪 | Scrolling performance. ☕️ | ## Sponsor & Support ☕️ diff --git a/docs/sidebars.ts b/docs/sidebars.ts index 5b7b92da..f1e316ed 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -26,6 +26,7 @@ const sidebars: SidebarsConfig = { 'crop', 'preview', + 'camera', ], } diff --git a/example/package.json b/example/package.json index 1fb14ac4..d0730306 100644 --- a/example/package.json +++ b/example/package.json @@ -1,6 +1,6 @@ { "name": "multipleimagepickerexample", - "version": "1.0.0", + "version": "2.0.4", "main": "expo/AppEntry.js", "scripts": { "start": "expo start", diff --git a/example/src/components/Button.tsx b/example/src/components/Button.tsx index a6d90a6f..6ceabcea 100644 --- a/example/src/components/Button.tsx +++ b/example/src/components/Button.tsx @@ -9,18 +9,35 @@ import { Text } from './Text' interface Props extends TouchableOpacityProps { children: React.ReactNode | string + type?: 'full' | 'outline' } -export function Button({ children, style: containerStyle, onPress }: Props) { +export function Button({ + children, + style: containerStyle, + onPress, + type = 'full', +}: Props) { const { foreground, background } = useTheme() + const isFull = type === 'full' return ( {typeof children === 'string' ? ( - {children} + + {children} + ) : ( children )} @@ -32,6 +49,7 @@ const style = StyleSheet.create({ button: { padding: 12, alignItems: 'center', + borderWidth: 1.5, }, text: { fontFamily: 'Avenir', diff --git a/example/src/components/Row.tsx b/example/src/components/Row.tsx index 08424aa5..338960fe 100644 --- a/example/src/components/Row.tsx +++ b/example/src/components/Row.tsx @@ -1,6 +1,6 @@ import React from 'react' -import { StyleSheet, ViewProps, ViewStyle } from 'react-native' -import { View } from './View' +import { StyleSheet, ViewStyle } from 'react-native' +import { View, ViewProps } from './View' export interface RowProps extends ViewProps { alignItems?: ViewStyle['alignItems'] diff --git a/example/src/components/View.tsx b/example/src/components/View.tsx index dd093423..75d9217c 100644 --- a/example/src/components/View.tsx +++ b/example/src/components/View.tsx @@ -1,8 +1,8 @@ import React from 'react' -import { View as RNView, ViewProps } from 'react-native' +import { View as RNView, ViewProps as RNViewProps } from 'react-native' import useTheme from '../hook/useTheme' -interface Props extends ViewProps { +export interface ViewProps extends RNViewProps { level?: 0 | 1 | 2 | 3 flex?: number } @@ -12,7 +12,7 @@ export function View({ style: containerStyle, level = 0, flex, -}: Props) { +}: ViewProps) { const theme = useTheme() const backgroundColor = !level ? theme.background diff --git a/example/src/index.tsx b/example/src/index.tsx index 00437220..f90bf10e 100644 --- a/example/src/index.tsx +++ b/example/src/index.tsx @@ -22,6 +22,7 @@ import { Config, openCropper, openPreview, + openCamera, } from '@baronha/react-native-multiple-image-picker' import { useImmer } from 'use-immer' import { StatusBar } from 'expo-status-bar' @@ -82,7 +83,7 @@ export default function App() { try { const response = await openPicker({ ...options, - selectedAssets: images, + selectedAssets: images.filter((item) => item.localIdentifier), }) setImages(Array.isArray(response) ? response : [response]) @@ -92,9 +93,22 @@ export default function App() { } } + const onCamera = async () => { + try { + const response = await openCamera() + + setImages((prev) => { + return [response as Result, ...prev] + }) + + layoutEffect() + } catch (e) { + console.log('e: ', e) + } + } + const onCrop = async () => { try { - console.log('images: ', images) const response = await openCropper(images[0].path, { ratio: [ { title: 'Instagram', width: 1, height: 1 }, @@ -167,7 +181,6 @@ export default function App() { width={WIDTH - 6} sourceKey={'path'} videoKey={'type'} - prefixPath={Platform.OS === 'ios' ? 'file://' : ''} conditionCheckVideo={'video'} videoURLKey={'thumbnail'} showDelete @@ -415,7 +428,7 @@ export default function App() { - setOptions('crop', value ? {} : undefined) + setOptions('crop', value ? true : undefined) } /> @@ -427,7 +440,11 @@ export default function App() { description="Enable crop circle functionality." > setOptions( 'crop', @@ -550,9 +567,14 @@ export default function App() { - + + + + @@ -643,4 +665,11 @@ const style = StyleSheet.create({ borderRadius: 6, borderWidth: 1, }, + bottom: { + padding: 16, + paddingHorizontal: 24, + }, + openPicker: { + flex: 1, + }, }) diff --git a/ios/Assets.swift b/ios/Assets.swift index 9e213212..383cbb19 100644 --- a/ios/Assets.swift +++ b/ios/Assets.swift @@ -20,6 +20,7 @@ class Assets { extension UIImage { static var close = UIImage(name: "close") + convenience init(name: String) { self.init(named: name, in: Assets.bundle(), compatibleWith: nil)! } diff --git a/ios/HybridMultipleImagePicker+Camera.swift b/ios/HybridMultipleImagePicker+Camera.swift index 32cfc49c..b837b317 100644 --- a/ios/HybridMultipleImagePicker+Camera.swift +++ b/ios/HybridMultipleImagePicker+Camera.swift @@ -5,10 +5,131 @@ // Created by BAO HA on 13/12/24. // +import AVFoundation import HXPhotoPicker +import Photos extension HybridMultipleImagePicker { -// func setCameraConfig(config: Camera) { -// // -// } + func openCamera(config: NitroCameraConfig, resolved: @escaping ((CameraResult) -> Void), rejected: @escaping ((Double) -> Void)) throws { + var captureType: CameraController.CaptureType = .all + + // check media type + switch config.mediaType { + case .image: + captureType = .photo + case .video: + captureType = .video + default: + break + } + + // config + var cameraConfig = CameraConfiguration() + + cameraConfig.videoMaximumDuration = config.videoMaximumDuration ?? 60 + + cameraConfig.modalPresentationStyle = self.setPresentation(config.presentation) + + cameraConfig.editor.modalPresentationStyle = .fullScreen + + if let crop = config.crop { + let editor = PickerCropConfig(circle: crop.circle, ratio: crop.ratio, defaultRatio: crop.defaultRatio, freeStyle: crop.freeStyle) + cameraConfig.editor = setCropConfig(editor) + } else { + cameraConfig.allowsEditing = false + } + + cameraConfig.languageType = setLocale(language: config.language) + cameraConfig.isSaveSystemAlbum = config.isSaveSystemAlbum ?? false + cameraConfig.sessionPreset = .hd4K3840x2160 + cameraConfig.aspectRatio = .fullScreen + + if let color = config.color, let focusColor = getReactColor(Int(color)) { + cameraConfig.focusColor = focusColor + } + + switch Int(config.cameraDevice?.rawValue ?? 1) { + case 0: + cameraConfig.position = .front + default: + cameraConfig.position = .back + } + + func getCameraResult(_ result: CameraController.Result, _ asset: PHAsset?) { + if let asset { + Task { + let photoAsset = PhotoAsset(asset) + let urlResult = try await photoAsset.urlResult() + let path = urlResult.url.absoluteString + + let phAsset = photoAsset.phAsset + let thumbnail = phAsset?.getVideoAssetThumbnail(from: path, in: 1) + + resolved(CameraResult(path: path, type: photoAsset.mediaType == .photo ? ResultType.image : ResultType.video, width: photoAsset.imageSize.width, height: photoAsset.imageSize.height, duration: photoAsset.videoDuration, thumbnail: thumbnail, fileName: phAsset?.fileName)) + } + + } else { + switch result { + case .image(let uiImage): + + let fileName = "IMG_\(Int(Date().timeIntervalSince1970)).jpg" + let filePath = uiImage.getPath(fileName: fileName, quality: 1.0) + + if let filePath { + resolved(CameraResult(path: filePath, type: ResultType.image, width: uiImage.size.width, height: uiImage.size.height, duration: nil, thumbnail: nil, fileName: fileName)) + } else { + rejected(1) + } + + case .video(let url): + + let asset = AVAsset(url: url) + + let thumbnail = getVideoThumbnail(from: url.absoluteString, in: 1) + + var result = CameraResult(path: "file://\(url.absoluteString)", + type: ResultType.video, + width: nil, + height: nil, + duration: asset.duration.seconds, + thumbnail: thumbnail, + fileName: url.lastPathComponent) + + if let track = asset.tracks(withMediaType: AVMediaType.video).first { + let trackSize = track.naturalSize.applying(track.preferredTransform) + let size = CGSize(width: abs(trackSize.width), height: abs(trackSize.height)) + + result.width = Double(size.width) + result.height = Double(size.height) + } + + resolved(result) + } + } + } + + DispatchQueue.main.async { + Photo.capture(cameraConfig, type: captureType) { result, asset, _ in + getCameraResult(result, asset) + } + } + } + + func setCameraConfig(_ options: PickerCameraConfig) -> SystemCameraConfiguration { + var config = SystemCameraConfiguration() + + config.editExportPreset = .highQuality + config.videoQuality = .typeHigh + + switch Int(options.cameraDevice?.rawValue ?? 1) { + case 0: + config.cameraDevice = .front + default: + config.cameraDevice = .rear + } + + config.videoMaximumDuration = options.videoMaximumDuration ?? 60 + + return config + } } diff --git a/ios/HybridMultipleImagePicker+Config.swift b/ios/HybridMultipleImagePicker+Config.swift index a47e126b..74b51941 100644 --- a/ios/HybridMultipleImagePicker+Config.swift +++ b/ios/HybridMultipleImagePicker+Config.swift @@ -124,21 +124,7 @@ extension HybridMultipleImagePicker { if let cameraOption = options.camera { photoList.allowAddCamera = true - var cameraConfig = SystemCameraConfiguration() - - cameraConfig.editExportPreset = .highQuality - cameraConfig.videoQuality = .typeHigh - - switch Int(cameraOption.cameraDevice.rawValue) { - case 0: - cameraConfig.cameraDevice = .front - default: - cameraConfig.cameraDevice = .rear - } - - cameraConfig.videoMaximumDuration = cameraOption.videoMaximumDuration ?? 60 - - photoList.cameraType = .system(cameraConfig) + photoList.cameraType = .system(setCameraConfig(cameraOption)) } else { photoList.allowAddCamera = false } @@ -149,12 +135,7 @@ extension HybridMultipleImagePicker { setLanguage(options) setTheme(options) - switch Int(options.presentation.rawValue) { - case 1: - config.modalPresentationStyle = .formSheet - default: - config.modalPresentationStyle = .fullScreen - } + config.modalPresentationStyle = setPresentation(options.presentation) } private func setTheme(_ options: NitroConfig) { @@ -203,6 +184,19 @@ extension HybridMultipleImagePicker { config.photoList.cell.customSelectableCellClass = nil } + func setPresentation(_ presentation: Presentation?) -> UIModalPresentationStyle { + if let presentation { + switch Int(presentation.rawValue) { + case 1: + return .formSheet + default: + return .fullScreen + } + } + + return .fullScreen + } + private func setLanguage(_ options: NitroConfig) { if let text = options.text { if let finish = text.finish { diff --git a/ios/HybridMultipleImagePicker+Preview.swift b/ios/HybridMultipleImagePicker+Preview.swift index 69d33183..f08f32c6 100644 --- a/ios/HybridMultipleImagePicker+Preview.swift +++ b/ios/HybridMultipleImagePicker+Preview.swift @@ -15,7 +15,7 @@ extension HybridMultipleImagePicker { var assets: [PhotoAsset] = [] previewConfig.tintColor = .white - previewConfig.videoPlayType = .auto + previewConfig.videoPlayType = config.videoAutoPlay == true ? .auto : .normal previewConfig.livePhotoPlayType = .auto previewConfig.languageType = setLocale(language: config.language) diff --git a/ios/HybridMultipleImagePicker+Result.swift b/ios/HybridMultipleImagePicker+Result.swift index f55e2606..88dab8ea 100644 --- a/ios/HybridMultipleImagePicker+Result.swift +++ b/ios/HybridMultipleImagePicker+Result.swift @@ -6,31 +6,23 @@ // import HXPhotoPicker -import Photos +// import Photos extension HybridMultipleImagePicker { func getResult(_ asset: PhotoAsset) async throws -> Result { let urlResult = try await asset.urlResult() let url = urlResult.url - + let creationDate = Int(asset.phAsset?.creationDate?.timeIntervalSince1970 ?? 0) - + let mime = url.getMimeType() - - let fileName = { - if let phAsset = asset.phAsset, let resources = PHAssetResource.assetResources(for: phAsset).first { - return resources.originalFilename - } - - return "" - }() - + + let phAsset = asset.phAsset + let type: ResultType = .init(fromString: asset.mediaType == .video ? "video" : "image")! - let thumbnail = asset.phAsset?.getVideoThumbnail(from: url.absoluteString, in: 1) - - return Result(path: url.absoluteString, - fileName: fileName, - localIdentifier: asset.phAsset!.localIdentifier, + let thumbnail = asset.phAsset?.getVideoAssetThumbnail(from: url.absoluteString, in: 1) + + return Result(localIdentifier: phAsset!.localIdentifier, width: asset.imageSize.width, height: asset.imageSize.height, mime: mime, @@ -39,9 +31,11 @@ extension HybridMultipleImagePicker { realPath: nil, parentFolderName: nil, creationDate: creationDate > 0 ? Double(creationDate) : nil, + crop: false, + path: "file://\(url.absoluteString)", type: type, duration: asset.videoDuration, thumbnail: thumbnail, - crop: false) + fileName: phAsset?.fileName) } } diff --git a/ios/PHAsset+Thumbnail.swift b/ios/PHAsset+Thumbnail.swift index b739cec0..f027b8fe 100644 --- a/ios/PHAsset+Thumbnail.swift +++ b/ios/PHAsset+Thumbnail.swift @@ -8,52 +8,48 @@ import Photos extension PHAsset { - func getVideoThumbnail(from moviePath: String, in seconds: Double) -> String? { + func getVideoAssetThumbnail(from moviePath: String, in seconds: Double) -> String? { if mediaType == .video { - let filepath = moviePath.replacingOccurrences(of: "file://", with: "") - let vidURL = URL(fileURLWithPath: filepath) - - let asset = AVURLAsset(url: vidURL, options: nil) - let generator = AVAssetImageGenerator(asset: asset) - generator.appliesPreferredTrackTransform = true - - let time = CMTime(seconds: seconds, preferredTimescale: 600) - - var thumbnail: UIImage? - - do { - let imgRef = try generator.copyCGImage(at: time, actualTime: nil) - thumbnail = UIImage(cgImage: imgRef) - } catch { - print("Error create thumbnail: \(error)") - return nil - } - - if let thumbnail { - return getImagePathFromUIImage(uiImage: thumbnail, prefix: "thumb") + if let path = getVideoThumbnail(from: moviePath, in: seconds) { + return "file://\(path)" } } return nil } - private func getImagePathFromUIImage(uiImage: UIImage, prefix: String? = "thumb") -> String? { - let fileManager = FileManager.default - - guard - let tempDirectory = FileManager.default.urls( - for: .cachesDirectory, - in: .userDomainMask).map(\.path).last - else { - return nil + var fileName: String { + if let resources = PHAssetResource.assetResources(for: self).first { + return resources.originalFilename } - let data = uiImage.jpegData(compressionQuality: 0.9) + return "" + } +} + +func getVideoThumbnail(from moviePath: String, in seconds: Double) -> String? { + let filepath = moviePath.replacingOccurrences(of: "file://", with: "") + let vidURL = URL(fileURLWithPath: filepath) - let fullPath = URL(fileURLWithPath: tempDirectory).appendingPathComponent("\(prefix ?? "thumb")-\(ProcessInfo.processInfo.globallyUniqueString).jpg").path + let asset = AVURLAsset(url: vidURL, options: nil) + let generator = AVAssetImageGenerator(asset: asset) + generator.appliesPreferredTrackTransform = true - fileManager.createFile(atPath: fullPath, contents: data, attributes: nil) + let time = CMTime(seconds: seconds, preferredTimescale: 600) - return "file://" + fullPath + var thumbnail: UIImage? + + do { + let imgRef = try generator.copyCGImage(at: time, actualTime: nil) + thumbnail = UIImage(cgImage: imgRef) + } catch { + print("Error create thumbnail: \(error)") + return nil } + + if let thumbnail { + return thumbnail.getPath(fileName: nil, quality: 0.8) + } + + return nil } diff --git a/ios/UIImage.swift b/ios/UIImage.swift new file mode 100644 index 00000000..376e504b --- /dev/null +++ b/ios/UIImage.swift @@ -0,0 +1,30 @@ +// +// UIImage.swift +// Pods +// +// Created by BAO HA on 16/12/24. +// + +extension UIImage { + func getPath(fileName name: String? = nil, quality: CGFloat = 1.0) -> String? { + let tempDirectoryURL = FileManager.default.temporaryDirectory + + let data = self.jpegData(compressionQuality: 0.9) + + let fileName = name ?? "IMG_\(Int(Date().timeIntervalSince1970)).jpg" + + let fileURL = tempDirectoryURL.appendingPathComponent(fileName) + + if let imageData = self.jpegData(compressionQuality: quality) { + do { + try imageData.write(to: fileURL) + return fileURL.absoluteString + + } catch { + return nil + } + } + + return nil + } +} diff --git a/nitrogen/generated/android/MultipleImagePickerOnLoad.cpp b/nitrogen/generated/android/MultipleImagePickerOnLoad.cpp index 8564a7ef..9fd7a974 100644 --- a/nitrogen/generated/android/MultipleImagePickerOnLoad.cpp +++ b/nitrogen/generated/android/MultipleImagePickerOnLoad.cpp @@ -15,6 +15,7 @@ #include "JFunc_void_std__vector_Result_.hpp" #include "JFunc_void_double.hpp" #include "JFunc_void_CropResult.hpp" +#include "JFunc_void_CameraResult.hpp" #include #include @@ -32,6 +33,8 @@ int initialize(JavaVM* vm) { margelo::nitro::multipleimagepicker::JFunc_void_double::registerNatives(); margelo::nitro::multipleimagepicker::JFunc_void_CropResult::registerNatives(); margelo::nitro::multipleimagepicker::JFunc_void_double::registerNatives(); + margelo::nitro::multipleimagepicker::JFunc_void_CameraResult::registerNatives(); + margelo::nitro::multipleimagepicker::JFunc_void_double::registerNatives(); // Register Nitro Hybrid Objects HybridObjectRegistry::registerHybridObjectConstructor( diff --git a/nitrogen/generated/android/c++/JCameraResult.hpp b/nitrogen/generated/android/c++/JCameraResult.hpp new file mode 100644 index 00000000..216c3f3a --- /dev/null +++ b/nitrogen/generated/android/c++/JCameraResult.hpp @@ -0,0 +1,79 @@ +/// +/// JCameraResult.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2024 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "CameraResult.hpp" + +#include "JResultType.hpp" +#include "ResultType.hpp" +#include +#include + +namespace margelo::nitro::multipleimagepicker { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "CameraResult" and the the Kotlin data class "CameraResult". + */ + struct JCameraResult final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/multipleimagepicker/CameraResult;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct CameraResult by copying all values to C++. + */ + [[maybe_unused]] + CameraResult toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldPath = clazz->getField("path"); + jni::local_ref path = this->getFieldValue(fieldPath); + static const auto fieldType = clazz->getField("type"); + jni::local_ref type = this->getFieldValue(fieldType); + static const auto fieldWidth = clazz->getField("width"); + jni::local_ref width = this->getFieldValue(fieldWidth); + static const auto fieldHeight = clazz->getField("height"); + jni::local_ref height = this->getFieldValue(fieldHeight); + static const auto fieldDuration = clazz->getField("duration"); + jni::local_ref duration = this->getFieldValue(fieldDuration); + static const auto fieldThumbnail = clazz->getField("thumbnail"); + jni::local_ref thumbnail = this->getFieldValue(fieldThumbnail); + static const auto fieldFileName = clazz->getField("fileName"); + jni::local_ref fileName = this->getFieldValue(fieldFileName); + return CameraResult( + path->toStdString(), + type->toCpp(), + width != nullptr ? std::make_optional(width->value()) : std::nullopt, + height != nullptr ? std::make_optional(height->value()) : std::nullopt, + duration != nullptr ? std::make_optional(duration->value()) : std::nullopt, + thumbnail != nullptr ? std::make_optional(thumbnail->toStdString()) : std::nullopt, + fileName != nullptr ? std::make_optional(fileName->toStdString()) : std::nullopt + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const CameraResult& value) { + return newInstance( + jni::make_jstring(value.path), + JResultType::fromCpp(value.type), + value.width.has_value() ? jni::JDouble::valueOf(value.width.value()) : nullptr, + value.height.has_value() ? jni::JDouble::valueOf(value.height.value()) : nullptr, + value.duration.has_value() ? jni::JDouble::valueOf(value.duration.value()) : nullptr, + value.thumbnail.has_value() ? jni::make_jstring(value.thumbnail.value()) : nullptr, + value.fileName.has_value() ? jni::make_jstring(value.fileName.value()) : nullptr + ); + } + }; + +} // namespace margelo::nitro::multipleimagepicker diff --git a/nitrogen/generated/android/c++/JFunc_void_CameraResult.hpp b/nitrogen/generated/android/c++/JFunc_void_CameraResult.hpp new file mode 100644 index 00000000..4be5fb27 --- /dev/null +++ b/nitrogen/generated/android/c++/JFunc_void_CameraResult.hpp @@ -0,0 +1,54 @@ +/// +/// JFunc_void_CameraResult.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2024 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include + +#include +#include "CameraResult.hpp" +#include "JCameraResult.hpp" +#include +#include "ResultType.hpp" +#include "JResultType.hpp" +#include + +namespace margelo::nitro::multipleimagepicker { + + using namespace facebook; + + /** + * C++ representation of the callback Func_void_CameraResult. + * This is a Kotlin `(result: CameraResult) -> Unit`, backed by a `std::function<...>`. + */ + struct JFunc_void_CameraResult final: public jni::HybridClass { + public: + static jni::local_ref fromCpp(const std::function& func) { + return JFunc_void_CameraResult::newObjectCxxArgs(func); + } + + public: + void call(jni::alias_ref result) { + return _func(result->toCpp()); + } + + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/multipleimagepicker/Func_void_CameraResult;"; + static void registerNatives() { + registerHybrid({makeNativeMethod("call", JFunc_void_CameraResult::call)}); + } + + private: + explicit JFunc_void_CameraResult(const std::function& func): _func(func) { } + + private: + friend HybridBase; + std::function _func; + }; + +} // namespace margelo::nitro::multipleimagepicker diff --git a/nitrogen/generated/android/c++/JHybridMultipleImagePickerSpec.cpp b/nitrogen/generated/android/c++/JHybridMultipleImagePickerSpec.cpp index 87c47bd7..3ec5df78 100644 --- a/nitrogen/generated/android/c++/JHybridMultipleImagePickerSpec.cpp +++ b/nitrogen/generated/android/c++/JHybridMultipleImagePickerSpec.cpp @@ -43,6 +43,10 @@ namespace margelo::nitro::multipleimagepicker { struct CropResult; } namespace margelo::nitro::multipleimagepicker { struct MediaPreview; } // Forward declaration of `NitroPreviewConfig` to properly resolve imports. namespace margelo::nitro::multipleimagepicker { struct NitroPreviewConfig; } +// Forward declaration of `NitroCameraConfig` to properly resolve imports. +namespace margelo::nitro::multipleimagepicker { struct NitroCameraConfig; } +// Forward declaration of `CameraResult` to properly resolve imports. +namespace margelo::nitro::multipleimagepicker { struct CameraResult; } #include "NitroConfig.hpp" #include "JNitroConfig.hpp" @@ -87,6 +91,11 @@ namespace margelo::nitro::multipleimagepicker { struct NitroPreviewConfig; } #include "JMediaPreview.hpp" #include "NitroPreviewConfig.hpp" #include "JNitroPreviewConfig.hpp" +#include "NitroCameraConfig.hpp" +#include "JNitroCameraConfig.hpp" +#include "CameraResult.hpp" +#include "JFunc_void_CameraResult.hpp" +#include "JCameraResult.hpp" namespace margelo::nitro::multipleimagepicker { @@ -129,5 +138,9 @@ namespace margelo::nitro::multipleimagepicker { return __array; }(), index, JNitroPreviewConfig::fromCpp(config)); } + void JHybridMultipleImagePickerSpec::openCamera(const NitroCameraConfig& config, const std::function& resolved, const std::function& rejected) { + static const auto method = _javaPart->getClass()->getMethod /* config */, jni::alias_ref /* resolved */, jni::alias_ref /* rejected */)>("openCamera"); + method(_javaPart, JNitroCameraConfig::fromCpp(config), JFunc_void_CameraResult::fromCpp(resolved), JFunc_void_double::fromCpp(rejected)); + } } // namespace margelo::nitro::multipleimagepicker diff --git a/nitrogen/generated/android/c++/JHybridMultipleImagePickerSpec.hpp b/nitrogen/generated/android/c++/JHybridMultipleImagePickerSpec.hpp index cb122563..d11601ec 100644 --- a/nitrogen/generated/android/c++/JHybridMultipleImagePickerSpec.hpp +++ b/nitrogen/generated/android/c++/JHybridMultipleImagePickerSpec.hpp @@ -54,6 +54,7 @@ namespace margelo::nitro::multipleimagepicker { void openPicker(const NitroConfig& config, const std::function& /* result */)>& resolved, const std::function& rejected) override; void openCrop(const std::string& image, const NitroCropConfig& config, const std::function& resolved, const std::function& rejected) override; void openPreview(const std::vector& media, double index, const NitroPreviewConfig& config) override; + void openCamera(const NitroCameraConfig& config, const std::function& resolved, const std::function& rejected) override; private: friend HybridBase; diff --git a/nitrogen/generated/android/c++/JNitroCameraConfig.hpp b/nitrogen/generated/android/c++/JNitroCameraConfig.hpp new file mode 100644 index 00000000..a9943dc4 --- /dev/null +++ b/nitrogen/generated/android/c++/JNitroCameraConfig.hpp @@ -0,0 +1,94 @@ +/// +/// JNitroCameraConfig.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2024 Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include "NitroCameraConfig.hpp" + +#include "CameraDevice.hpp" +#include "CropRatio.hpp" +#include "JCameraDevice.hpp" +#include "JCropRatio.hpp" +#include "JLanguage.hpp" +#include "JMediaType.hpp" +#include "JPickerCropConfig.hpp" +#include "JPresentation.hpp" +#include "Language.hpp" +#include "MediaType.hpp" +#include "PickerCropConfig.hpp" +#include "Presentation.hpp" +#include +#include +#include + +namespace margelo::nitro::multipleimagepicker { + + using namespace facebook; + + /** + * The C++ JNI bridge between the C++ struct "NitroCameraConfig" and the the Kotlin data class "NitroCameraConfig". + */ + struct JNitroCameraConfig final: public jni::JavaClass { + public: + static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/multipleimagepicker/NitroCameraConfig;"; + + public: + /** + * Convert this Java/Kotlin-based struct to the C++ struct NitroCameraConfig by copying all values to C++. + */ + [[maybe_unused]] + NitroCameraConfig toCpp() const { + static const auto clazz = javaClassStatic(); + static const auto fieldMediaType = clazz->getField("mediaType"); + jni::local_ref mediaType = this->getFieldValue(fieldMediaType); + static const auto fieldPresentation = clazz->getField("presentation"); + jni::local_ref presentation = this->getFieldValue(fieldPresentation); + static const auto fieldLanguage = clazz->getField("language"); + jni::local_ref language = this->getFieldValue(fieldLanguage); + static const auto fieldCrop = clazz->getField("crop"); + jni::local_ref crop = this->getFieldValue(fieldCrop); + static const auto fieldIsSaveSystemAlbum = clazz->getField("isSaveSystemAlbum"); + jni::local_ref isSaveSystemAlbum = this->getFieldValue(fieldIsSaveSystemAlbum); + static const auto fieldColor = clazz->getField("color"); + jni::local_ref color = this->getFieldValue(fieldColor); + static const auto fieldCameraDevice = clazz->getField("cameraDevice"); + jni::local_ref cameraDevice = this->getFieldValue(fieldCameraDevice); + static const auto fieldVideoMaximumDuration = clazz->getField("videoMaximumDuration"); + jni::local_ref videoMaximumDuration = this->getFieldValue(fieldVideoMaximumDuration); + return NitroCameraConfig( + mediaType->toCpp(), + presentation->toCpp(), + language->toCpp(), + crop != nullptr ? std::make_optional(crop->toCpp()) : std::nullopt, + isSaveSystemAlbum != nullptr ? std::make_optional(static_cast(isSaveSystemAlbum->value())) : std::nullopt, + color != nullptr ? std::make_optional(color->value()) : std::nullopt, + cameraDevice != nullptr ? std::make_optional(cameraDevice->toCpp()) : std::nullopt, + videoMaximumDuration != nullptr ? std::make_optional(videoMaximumDuration->value()) : std::nullopt + ); + } + + public: + /** + * Create a Java/Kotlin-based struct by copying all values from the given C++ struct to Java. + */ + [[maybe_unused]] + static jni::local_ref fromCpp(const NitroCameraConfig& value) { + return newInstance( + JMediaType::fromCpp(value.mediaType), + JPresentation::fromCpp(value.presentation), + JLanguage::fromCpp(value.language), + value.crop.has_value() ? JPickerCropConfig::fromCpp(value.crop.value()) : nullptr, + value.isSaveSystemAlbum.has_value() ? jni::JBoolean::valueOf(value.isSaveSystemAlbum.value()) : nullptr, + value.color.has_value() ? jni::JDouble::valueOf(value.color.value()) : nullptr, + value.cameraDevice.has_value() ? JCameraDevice::fromCpp(value.cameraDevice.value()) : nullptr, + value.videoMaximumDuration.has_value() ? jni::JDouble::valueOf(value.videoMaximumDuration.value()) : nullptr + ); + } + }; + +} // namespace margelo::nitro::multipleimagepicker diff --git a/nitrogen/generated/android/c++/JNitroPreviewConfig.hpp b/nitrogen/generated/android/c++/JNitroPreviewConfig.hpp index 0e829f97..d893ce03 100644 --- a/nitrogen/generated/android/c++/JNitroPreviewConfig.hpp +++ b/nitrogen/generated/android/c++/JNitroPreviewConfig.hpp @@ -12,6 +12,7 @@ #include "JLanguage.hpp" #include "Language.hpp" +#include namespace margelo::nitro::multipleimagepicker { @@ -33,8 +34,11 @@ namespace margelo::nitro::multipleimagepicker { static const auto clazz = javaClassStatic(); static const auto fieldLanguage = clazz->getField("language"); jni::local_ref language = this->getFieldValue(fieldLanguage); + static const auto fieldVideoAutoPlay = clazz->getField("videoAutoPlay"); + jni::local_ref videoAutoPlay = this->getFieldValue(fieldVideoAutoPlay); return NitroPreviewConfig( - language->toCpp() + language->toCpp(), + videoAutoPlay != nullptr ? std::make_optional(static_cast(videoAutoPlay->value())) : std::nullopt ); } @@ -45,7 +49,8 @@ namespace margelo::nitro::multipleimagepicker { [[maybe_unused]] static jni::local_ref fromCpp(const NitroPreviewConfig& value) { return newInstance( - JLanguage::fromCpp(value.language) + JLanguage::fromCpp(value.language), + value.videoAutoPlay.has_value() ? jni::JBoolean::valueOf(value.videoAutoPlay.value()) : nullptr ); } }; diff --git a/nitrogen/generated/android/c++/JPickerCameraConfig.hpp b/nitrogen/generated/android/c++/JPickerCameraConfig.hpp index 74168638..7de9868a 100644 --- a/nitrogen/generated/android/c++/JPickerCameraConfig.hpp +++ b/nitrogen/generated/android/c++/JPickerCameraConfig.hpp @@ -37,7 +37,7 @@ namespace margelo::nitro::multipleimagepicker { static const auto fieldVideoMaximumDuration = clazz->getField("videoMaximumDuration"); jni::local_ref videoMaximumDuration = this->getFieldValue(fieldVideoMaximumDuration); return PickerCameraConfig( - cameraDevice->toCpp(), + cameraDevice != nullptr ? std::make_optional(cameraDevice->toCpp()) : std::nullopt, videoMaximumDuration != nullptr ? std::make_optional(videoMaximumDuration->value()) : std::nullopt ); } @@ -49,7 +49,7 @@ namespace margelo::nitro::multipleimagepicker { [[maybe_unused]] static jni::local_ref fromCpp(const PickerCameraConfig& value) { return newInstance( - JCameraDevice::fromCpp(value.cameraDevice), + value.cameraDevice.has_value() ? JCameraDevice::fromCpp(value.cameraDevice.value()) : nullptr, value.videoMaximumDuration.has_value() ? jni::JDouble::valueOf(value.videoMaximumDuration.value()) : nullptr ); } diff --git a/nitrogen/generated/android/c++/JResult.hpp b/nitrogen/generated/android/c++/JResult.hpp index 13e36e32..6c526284 100644 --- a/nitrogen/generated/android/c++/JResult.hpp +++ b/nitrogen/generated/android/c++/JResult.hpp @@ -33,10 +33,6 @@ namespace margelo::nitro::multipleimagepicker { [[maybe_unused]] Result toCpp() const { static const auto clazz = javaClassStatic(); - static const auto fieldPath = clazz->getField("path"); - jni::local_ref path = this->getFieldValue(fieldPath); - static const auto fieldFileName = clazz->getField("fileName"); - jni::local_ref fileName = this->getFieldValue(fieldFileName); static const auto fieldLocalIdentifier = clazz->getField("localIdentifier"); jni::local_ref localIdentifier = this->getFieldValue(fieldLocalIdentifier); static const auto fieldWidth = clazz->getField("width"); @@ -55,17 +51,19 @@ namespace margelo::nitro::multipleimagepicker { jni::local_ref parentFolderName = this->getFieldValue(fieldParentFolderName); static const auto fieldCreationDate = clazz->getField("creationDate"); jni::local_ref creationDate = this->getFieldValue(fieldCreationDate); + static const auto fieldCrop = clazz->getField("crop"); + jni::local_ref crop = this->getFieldValue(fieldCrop); + static const auto fieldPath = clazz->getField("path"); + jni::local_ref path = this->getFieldValue(fieldPath); static const auto fieldType = clazz->getField("type"); jni::local_ref type = this->getFieldValue(fieldType); static const auto fieldDuration = clazz->getField("duration"); jni::local_ref duration = this->getFieldValue(fieldDuration); static const auto fieldThumbnail = clazz->getField("thumbnail"); jni::local_ref thumbnail = this->getFieldValue(fieldThumbnail); - static const auto fieldCrop = clazz->getField("crop"); - jni::local_ref crop = this->getFieldValue(fieldCrop); + static const auto fieldFileName = clazz->getField("fileName"); + jni::local_ref fileName = this->getFieldValue(fieldFileName); return Result( - path->toStdString(), - fileName->toStdString(), localIdentifier->toStdString(), width, height, @@ -75,10 +73,12 @@ namespace margelo::nitro::multipleimagepicker { realPath != nullptr ? std::make_optional(realPath->toStdString()) : std::nullopt, parentFolderName != nullptr ? std::make_optional(parentFolderName->toStdString()) : std::nullopt, creationDate != nullptr ? std::make_optional(creationDate->value()) : std::nullopt, + crop != nullptr ? std::make_optional(static_cast(crop->value())) : std::nullopt, + path->toStdString(), type->toCpp(), duration != nullptr ? std::make_optional(duration->value()) : std::nullopt, thumbnail != nullptr ? std::make_optional(thumbnail->toStdString()) : std::nullopt, - crop != nullptr ? std::make_optional(static_cast(crop->value())) : std::nullopt + fileName != nullptr ? std::make_optional(fileName->toStdString()) : std::nullopt ); } @@ -89,8 +89,6 @@ namespace margelo::nitro::multipleimagepicker { [[maybe_unused]] static jni::local_ref fromCpp(const Result& value) { return newInstance( - jni::make_jstring(value.path), - jni::make_jstring(value.fileName), jni::make_jstring(value.localIdentifier), value.width, value.height, @@ -100,10 +98,12 @@ namespace margelo::nitro::multipleimagepicker { value.realPath.has_value() ? jni::make_jstring(value.realPath.value()) : nullptr, value.parentFolderName.has_value() ? jni::make_jstring(value.parentFolderName.value()) : nullptr, value.creationDate.has_value() ? jni::JDouble::valueOf(value.creationDate.value()) : nullptr, + value.crop.has_value() ? jni::JBoolean::valueOf(value.crop.value()) : nullptr, + jni::make_jstring(value.path), JResultType::fromCpp(value.type), value.duration.has_value() ? jni::JDouble::valueOf(value.duration.value()) : nullptr, value.thumbnail.has_value() ? jni::make_jstring(value.thumbnail.value()) : nullptr, - value.crop.has_value() ? jni::JBoolean::valueOf(value.crop.value()) : nullptr + value.fileName.has_value() ? jni::make_jstring(value.fileName.value()) : nullptr ); } }; diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/CameraResult.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/CameraResult.kt new file mode 100644 index 00000000..613409e7 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/CameraResult.kt @@ -0,0 +1,27 @@ +/// +/// CameraResult.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2024 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.multipleimagepicker + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* + +/** + * Represents the JavaScript object/struct "CameraResult". + */ +@DoNotStrip +@Keep +data class CameraResult( + val path: String, + val type: ResultType, + val width: Double?, + val height: Double?, + val duration: Double?, + val thumbnail: String?, + val fileName: String? +) diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/Func_void_CameraResult.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/Func_void_CameraResult.kt new file mode 100644 index 00000000..18b3e2f7 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/Func_void_CameraResult.kt @@ -0,0 +1,46 @@ +/// +/// Func_void_CameraResult.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2024 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.multipleimagepicker + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* +import dalvik.annotation.optimization.FastNative + +/** + * Represents the JavaScript callback `(result: struct) => void`. + * This is implemented in C++, via a `std::function<...>`. + */ +@DoNotStrip +@Keep +@Suppress("RedundantSuppression", "ConvertSecondaryConstructorToPrimary", "RedundantUnitReturnType", "KotlinJniMissingFunction", "ClassName", "unused") +class Func_void_CameraResult { + @DoNotStrip + @Keep + private val mHybridData: HybridData + + @DoNotStrip + @Keep + private constructor(hybridData: HybridData) { + mHybridData = hybridData + } + + /** + * Converts this function to a Kotlin Lambda. + * This exists purely as syntactic sugar, and has minimal runtime overhead. + */ + fun toLambda(): (result: CameraResult) -> Unit = this::call + + /** + * Call the given JS callback. + * @throws Throwable if the JS function itself throws an error, or if the JS function/runtime has already been deleted. + */ + @FastNative + external fun call(result: CameraResult): Unit +} diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/HybridMultipleImagePickerSpec.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/HybridMultipleImagePickerSpec.kt index 9e941926..caf39b02 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/HybridMultipleImagePickerSpec.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/HybridMultipleImagePickerSpec.kt @@ -66,6 +66,17 @@ abstract class HybridMultipleImagePickerSpec: HybridObject() { @DoNotStrip @Keep abstract fun openPreview(media: Array, index: Double, config: NitroPreviewConfig): Unit + + @DoNotStrip + @Keep + abstract fun openCamera(config: NitroCameraConfig, resolved: (result: CameraResult) -> Unit, rejected: (reject: Double) -> Unit): Unit + + @DoNotStrip + @Keep + private fun openCamera(config: NitroCameraConfig, resolved: Func_void_CameraResult, rejected: Func_void_double): Unit { + val __result = openCamera(config, resolved.toLambda(), rejected.toLambda()) + return __result + } private external fun initHybrid(): HybridData diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/NitroCameraConfig.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/NitroCameraConfig.kt new file mode 100644 index 00000000..a0c299d0 --- /dev/null +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/NitroCameraConfig.kt @@ -0,0 +1,28 @@ +/// +/// NitroCameraConfig.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2024 Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.multipleimagepicker + +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.* + +/** + * Represents the JavaScript object/struct "NitroCameraConfig". + */ +@DoNotStrip +@Keep +data class NitroCameraConfig( + val mediaType: MediaType, + val presentation: Presentation, + val language: Language, + val crop: PickerCropConfig?, + val isSaveSystemAlbum: Boolean?, + val color: Double?, + val cameraDevice: CameraDevice?, + val videoMaximumDuration: Double? +) diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/NitroPreviewConfig.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/NitroPreviewConfig.kt index fc5785d6..c6793f4e 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/NitroPreviewConfig.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/NitroPreviewConfig.kt @@ -17,5 +17,6 @@ import com.margelo.nitro.core.* @DoNotStrip @Keep data class NitroPreviewConfig( - val language: Language + val language: Language, + val videoAutoPlay: Boolean? ) diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/PickerCameraConfig.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/PickerCameraConfig.kt index afd67231..013b3285 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/PickerCameraConfig.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/PickerCameraConfig.kt @@ -17,6 +17,6 @@ import com.margelo.nitro.core.* @DoNotStrip @Keep data class PickerCameraConfig( - val cameraDevice: CameraDevice, + val cameraDevice: CameraDevice?, val videoMaximumDuration: Double? ) diff --git a/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/Result.kt b/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/Result.kt index 4a31525c..60d06ab2 100644 --- a/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/Result.kt +++ b/nitrogen/generated/android/kotlin/com/margelo/nitro/multipleimagepicker/Result.kt @@ -17,8 +17,6 @@ import com.margelo.nitro.core.* @DoNotStrip @Keep data class Result( - val path: String, - val fileName: String, val localIdentifier: String, val width: Double, val height: Double, @@ -28,8 +26,10 @@ data class Result( val realPath: String?, val parentFolderName: String?, val creationDate: Double?, + val crop: Boolean?, + val path: String, val type: ResultType, val duration: Double?, val thumbnail: String?, - val crop: Boolean? + val fileName: String? ) diff --git a/nitrogen/generated/ios/MultipleImagePicker-Swift-Cxx-Bridge.hpp b/nitrogen/generated/ios/MultipleImagePicker-Swift-Cxx-Bridge.hpp index 5f6b142c..2d109b33 100644 --- a/nitrogen/generated/ios/MultipleImagePicker-Swift-Cxx-Bridge.hpp +++ b/nitrogen/generated/ios/MultipleImagePicker-Swift-Cxx-Bridge.hpp @@ -10,6 +10,8 @@ // Forward declarations of C++ defined types // Forward declaration of `CameraDevice` to properly resolve imports. namespace margelo::nitro::multipleimagepicker { enum class CameraDevice; } +// Forward declaration of `CameraResult` to properly resolve imports. +namespace margelo::nitro::multipleimagepicker { struct CameraResult; } // Forward declaration of `CropRatio` to properly resolve imports. namespace margelo::nitro::multipleimagepicker { struct CropRatio; } // Forward declaration of `CropResult` to properly resolve imports. @@ -35,6 +37,7 @@ namespace MultipleImagePicker { class HybridMultipleImagePickerSpecCxx; } // Include C++ defined types #include "CameraDevice.hpp" +#include "CameraResult.hpp" #include "CropRatio.hpp" #include "CropResult.hpp" #include "HybridMultipleImagePickerSpec.hpp" @@ -132,6 +135,15 @@ namespace margelo::nitro::multipleimagepicker::bridge::swift { return std::optional(value); } + // pragma MARK: std::optional + /** + * Specialized version of `std::optional`. + */ + using std__optional_CameraDevice_ = std::optional; + inline std::optional create_std__optional_CameraDevice_(const CameraDevice& value) { + return std::optional(value); + } + // pragma MARK: std::optional /** * Specialized version of `std::optional`. @@ -236,6 +248,34 @@ namespace margelo::nitro::multipleimagepicker::bridge::swift { return vector; } + // pragma MARK: std::function + /** + * Specialized version of `std::function`. + */ + using Func_void_CameraResult = std::function; + /** + * Wrapper class for a `std::function`, this can be used from Swift. + */ + class Func_void_CameraResult_Wrapper final { + public: + explicit Func_void_CameraResult_Wrapper(const std::function& func): _function(func) {} + explicit Func_void_CameraResult_Wrapper(std::function&& func): _function(std::move(func)) {} + inline void call(CameraResult result) const { + _function(result); + } + private: + std::function _function; + }; + inline Func_void_CameraResult create_Func_void_CameraResult(void* _Nonnull closureHolder, void(* _Nonnull call)(void* _Nonnull /* closureHolder */, CameraResult), void(* _Nonnull destroy)(void* _Nonnull)) { + std::shared_ptr sharedClosureHolder(closureHolder, destroy); + return Func_void_CameraResult([sharedClosureHolder, call](const CameraResult& result) -> void { + call(sharedClosureHolder.get(), result); + }); + } + inline std::shared_ptr share_Func_void_CameraResult(const Func_void_CameraResult& value) { + return std::make_shared(value); + } + // pragma MARK: std::shared_ptr /** * Specialized version of `std::shared_ptr`. diff --git a/nitrogen/generated/ios/MultipleImagePicker-Swift-Cxx-Umbrella.hpp b/nitrogen/generated/ios/MultipleImagePicker-Swift-Cxx-Umbrella.hpp index 1d15092a..fc293efb 100644 --- a/nitrogen/generated/ios/MultipleImagePicker-Swift-Cxx-Umbrella.hpp +++ b/nitrogen/generated/ios/MultipleImagePicker-Swift-Cxx-Umbrella.hpp @@ -10,6 +10,8 @@ // Forward declarations of C++ defined types // Forward declaration of `CameraDevice` to properly resolve imports. namespace margelo::nitro::multipleimagepicker { enum class CameraDevice; } +// Forward declaration of `CameraResult` to properly resolve imports. +namespace margelo::nitro::multipleimagepicker { struct CameraResult; } // Forward declaration of `CropRatio` to properly resolve imports. namespace margelo::nitro::multipleimagepicker { struct CropRatio; } // Forward declaration of `CropResult` to properly resolve imports. @@ -22,6 +24,8 @@ namespace margelo::nitro::multipleimagepicker { enum class Language; } namespace margelo::nitro::multipleimagepicker { struct MediaPreview; } // Forward declaration of `MediaType` to properly resolve imports. namespace margelo::nitro::multipleimagepicker { enum class MediaType; } +// Forward declaration of `NitroCameraConfig` to properly resolve imports. +namespace margelo::nitro::multipleimagepicker { struct NitroCameraConfig; } // Forward declaration of `NitroConfig` to properly resolve imports. namespace margelo::nitro::multipleimagepicker { struct NitroConfig; } // Forward declaration of `NitroCropConfig` to properly resolve imports. @@ -49,12 +53,14 @@ namespace margelo::nitro::multipleimagepicker { enum class Theme; } // Include C++ defined types #include "CameraDevice.hpp" +#include "CameraResult.hpp" #include "CropRatio.hpp" #include "CropResult.hpp" #include "HybridMultipleImagePickerSpec.hpp" #include "Language.hpp" #include "MediaPreview.hpp" #include "MediaType.hpp" +#include "NitroCameraConfig.hpp" #include "NitroConfig.hpp" #include "NitroCropConfig.hpp" #include "NitroPreviewConfig.hpp" diff --git a/nitrogen/generated/ios/c++/HybridMultipleImagePickerSpecSwift.hpp b/nitrogen/generated/ios/c++/HybridMultipleImagePickerSpecSwift.hpp index 37aa344f..5d333b30 100644 --- a/nitrogen/generated/ios/c++/HybridMultipleImagePickerSpecSwift.hpp +++ b/nitrogen/generated/ios/c++/HybridMultipleImagePickerSpecSwift.hpp @@ -48,6 +48,10 @@ namespace margelo::nitro::multipleimagepicker { struct CropResult; } namespace margelo::nitro::multipleimagepicker { struct MediaPreview; } // Forward declaration of `NitroPreviewConfig` to properly resolve imports. namespace margelo::nitro::multipleimagepicker { struct NitroPreviewConfig; } +// Forward declaration of `NitroCameraConfig` to properly resolve imports. +namespace margelo::nitro::multipleimagepicker { struct NitroCameraConfig; } +// Forward declaration of `CameraResult` to properly resolve imports. +namespace margelo::nitro::multipleimagepicker { struct CameraResult; } #include "NitroConfig.hpp" #include "MediaType.hpp" @@ -71,6 +75,8 @@ namespace margelo::nitro::multipleimagepicker { struct NitroPreviewConfig; } #include "CropResult.hpp" #include "MediaPreview.hpp" #include "NitroPreviewConfig.hpp" +#include "NitroCameraConfig.hpp" +#include "CameraResult.hpp" #if __has_include() #include @@ -124,6 +130,9 @@ namespace margelo::nitro::multipleimagepicker { inline void openPreview(const std::vector& media, double index, const NitroPreviewConfig& config) override { _swiftPart.openPreview(media, std::forward(index), config); } + inline void openCamera(const NitroCameraConfig& config, const std::function& resolved, const std::function& rejected) override { + _swiftPart.openCamera(config, resolved, rejected); + } private: MultipleImagePicker::HybridMultipleImagePickerSpecCxx _swiftPart; diff --git a/nitrogen/generated/ios/swift/CameraResult.swift b/nitrogen/generated/ios/swift/CameraResult.swift new file mode 100644 index 00000000..763a241c --- /dev/null +++ b/nitrogen/generated/ios/swift/CameraResult.swift @@ -0,0 +1,173 @@ +/// +/// CameraResult.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2024 Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * Represents an instance of `CameraResult`, backed by a C++ struct. + */ +public typealias CameraResult = margelo.nitro.multipleimagepicker.CameraResult + +public extension CameraResult { + private typealias bridge = margelo.nitro.multipleimagepicker.bridge.swift + + /** + * Create a new instance of `CameraResult`. + */ + init(path: String, type: ResultType, width: Double?, height: Double?, duration: Double?, thumbnail: String?, fileName: String?) { + self.init(std.string(path), type, { () -> bridge.std__optional_double_ in + if let __unwrappedValue = width { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_double_ in + if let __unwrappedValue = height { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_double_ in + if let __unwrappedValue = duration { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = thumbnail { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) + } else { + return .init() + } + }(), { () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = fileName { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) + } else { + return .init() + } + }()) + } + + var path: String { + @inline(__always) + get { + return String(self.__path) + } + @inline(__always) + set { + self.__path = std.string(newValue) + } + } + + var type: ResultType { + @inline(__always) + get { + return self.__type + } + @inline(__always) + set { + self.__type = newValue + } + } + + var width: Double? { + @inline(__always) + get { + return self.__width.value + } + @inline(__always) + set { + self.__width = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var height: Double? { + @inline(__always) + get { + return self.__height.value + } + @inline(__always) + set { + self.__height = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var duration: Double? { + @inline(__always) + get { + return self.__duration.value + } + @inline(__always) + set { + self.__duration = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var thumbnail: String? { + @inline(__always) + get { + return { () -> String? in + if let __unwrapped = self.__thumbnail.value { + return String(__unwrapped) + } else { + return nil + } + }() + } + @inline(__always) + set { + self.__thumbnail = { () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) + } else { + return .init() + } + }() + } + } + + var fileName: String? { + @inline(__always) + get { + return { () -> String? in + if let __unwrapped = self.__fileName.value { + return String(__unwrapped) + } else { + return nil + } + }() + } + @inline(__always) + set { + self.__fileName = { () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) + } else { + return .init() + } + }() + } + } +} diff --git a/nitrogen/generated/ios/swift/HybridMultipleImagePickerSpec.swift b/nitrogen/generated/ios/swift/HybridMultipleImagePickerSpec.swift index f71ac75c..a394edf9 100644 --- a/nitrogen/generated/ios/swift/HybridMultipleImagePickerSpec.swift +++ b/nitrogen/generated/ios/swift/HybridMultipleImagePickerSpec.swift @@ -35,4 +35,5 @@ public protocol HybridMultipleImagePickerSpec: AnyObject, HybridObjectSpec { func openPicker(config: NitroConfig, resolved: @escaping ((_ result: [Result]) -> Void), rejected: @escaping ((_ reject: Double) -> Void)) throws -> Void func openCrop(image: String, config: NitroCropConfig, resolved: @escaping ((_ result: CropResult) -> Void), rejected: @escaping ((_ reject: Double) -> Void)) throws -> Void func openPreview(media: [MediaPreview], index: Double, config: NitroPreviewConfig) throws -> Void + func openCamera(config: NitroCameraConfig, resolved: @escaping ((_ result: CameraResult) -> Void), rejected: @escaping ((_ reject: Double) -> Void)) throws -> Void } diff --git a/nitrogen/generated/ios/swift/HybridMultipleImagePickerSpecCxx.swift b/nitrogen/generated/ios/swift/HybridMultipleImagePickerSpecCxx.swift index 1be82311..e4035bcf 100644 --- a/nitrogen/generated/ios/swift/HybridMultipleImagePickerSpecCxx.swift +++ b/nitrogen/generated/ios/swift/HybridMultipleImagePickerSpecCxx.swift @@ -156,4 +156,25 @@ public class HybridMultipleImagePickerSpecCxx { fatalError("Swift errors can currently not be propagated to C++! See https://github.com/swiftlang/swift/issues/75290 (Error: \(__message))") } } + + @inline(__always) + public func openCamera(config: NitroCameraConfig, resolved: bridge.Func_void_CameraResult, rejected: bridge.Func_void_double) -> Void { + do { + try self.__implementation.openCamera(config: config, resolved: { () -> ((CameraResult) -> Void) in + let __sharedClosure = bridge.share_Func_void_CameraResult(resolved) + return { (__result: CameraResult) -> Void in + __sharedClosure.pointee.call(__result) + } + }(), rejected: { () -> ((Double) -> Void) in + let __sharedClosure = bridge.share_Func_void_double(rejected) + return { (__reject: Double) -> Void in + __sharedClosure.pointee.call(__reject) + } + }()) + return + } catch { + let __message = "\(error.localizedDescription)" + fatalError("Swift errors can currently not be propagated to C++! See https://github.com/swiftlang/swift/issues/75290 (Error: \(__message))") + } + } } diff --git a/nitrogen/generated/ios/swift/NitroCameraConfig.swift b/nitrogen/generated/ios/swift/NitroCameraConfig.swift new file mode 100644 index 00000000..ae538c02 --- /dev/null +++ b/nitrogen/generated/ios/swift/NitroCameraConfig.swift @@ -0,0 +1,178 @@ +/// +/// NitroCameraConfig.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2024 Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * Represents an instance of `NitroCameraConfig`, backed by a C++ struct. + */ +public typealias NitroCameraConfig = margelo.nitro.multipleimagepicker.NitroCameraConfig + +public extension NitroCameraConfig { + private typealias bridge = margelo.nitro.multipleimagepicker.bridge.swift + + /** + * Create a new instance of `NitroCameraConfig`. + */ + init(mediaType: MediaType, presentation: Presentation, language: Language, crop: PickerCropConfig?, isSaveSystemAlbum: Bool?, color: Double?, cameraDevice: CameraDevice?, videoMaximumDuration: Double?) { + self.init(mediaType, presentation, language, { () -> bridge.std__optional_PickerCropConfig_ in + if let __unwrappedValue = crop { + return bridge.create_std__optional_PickerCropConfig_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_bool_ in + if let __unwrappedValue = isSaveSystemAlbum { + return bridge.create_std__optional_bool_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_double_ in + if let __unwrappedValue = color { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_CameraDevice_ in + if let __unwrappedValue = cameraDevice { + return bridge.create_std__optional_CameraDevice_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_double_ in + if let __unwrappedValue = videoMaximumDuration { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }()) + } + + var mediaType: MediaType { + @inline(__always) + get { + return self.__mediaType + } + @inline(__always) + set { + self.__mediaType = newValue + } + } + + var presentation: Presentation { + @inline(__always) + get { + return self.__presentation + } + @inline(__always) + set { + self.__presentation = newValue + } + } + + var language: Language { + @inline(__always) + get { + return self.__language + } + @inline(__always) + set { + self.__language = newValue + } + } + + var crop: PickerCropConfig? { + @inline(__always) + get { + return { () -> PickerCropConfig? in + if let __unwrapped = self.__crop.value { + return __unwrapped + } else { + return nil + } + }() + } + @inline(__always) + set { + self.__crop = { () -> bridge.std__optional_PickerCropConfig_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_PickerCropConfig_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var isSaveSystemAlbum: Bool? { + @inline(__always) + get { + return self.__isSaveSystemAlbum.value + } + @inline(__always) + set { + self.__isSaveSystemAlbum = { () -> bridge.std__optional_bool_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_bool_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var color: Double? { + @inline(__always) + get { + return self.__color.value + } + @inline(__always) + set { + self.__color = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var cameraDevice: CameraDevice? { + @inline(__always) + get { + return self.__cameraDevice.value + } + @inline(__always) + set { + self.__cameraDevice = { () -> bridge.std__optional_CameraDevice_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_CameraDevice_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var videoMaximumDuration: Double? { + @inline(__always) + get { + return self.__videoMaximumDuration.value + } + @inline(__always) + set { + self.__videoMaximumDuration = { () -> bridge.std__optional_double_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_double_(__unwrappedValue) + } else { + return .init() + } + }() + } + } +} diff --git a/nitrogen/generated/ios/swift/NitroPreviewConfig.swift b/nitrogen/generated/ios/swift/NitroPreviewConfig.swift index 00fd3127..a8dabaca 100644 --- a/nitrogen/generated/ios/swift/NitroPreviewConfig.swift +++ b/nitrogen/generated/ios/swift/NitroPreviewConfig.swift @@ -18,8 +18,14 @@ public extension NitroPreviewConfig { /** * Create a new instance of `NitroPreviewConfig`. */ - init(language: Language) { - self.init(language) + init(language: Language, videoAutoPlay: Bool?) { + self.init(language, { () -> bridge.std__optional_bool_ in + if let __unwrappedValue = videoAutoPlay { + return bridge.create_std__optional_bool_(__unwrappedValue) + } else { + return .init() + } + }()) } var language: Language { @@ -32,4 +38,21 @@ public extension NitroPreviewConfig { self.__language = newValue } } + + var videoAutoPlay: Bool? { + @inline(__always) + get { + return self.__videoAutoPlay.value + } + @inline(__always) + set { + self.__videoAutoPlay = { () -> bridge.std__optional_bool_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_bool_(__unwrappedValue) + } else { + return .init() + } + }() + } + } } diff --git a/nitrogen/generated/ios/swift/PickerCameraConfig.swift b/nitrogen/generated/ios/swift/PickerCameraConfig.swift index 889b54f4..4bbf216a 100644 --- a/nitrogen/generated/ios/swift/PickerCameraConfig.swift +++ b/nitrogen/generated/ios/swift/PickerCameraConfig.swift @@ -18,8 +18,14 @@ public extension PickerCameraConfig { /** * Create a new instance of `PickerCameraConfig`. */ - init(cameraDevice: CameraDevice, videoMaximumDuration: Double?) { - self.init(cameraDevice, { () -> bridge.std__optional_double_ in + init(cameraDevice: CameraDevice?, videoMaximumDuration: Double?) { + self.init({ () -> bridge.std__optional_CameraDevice_ in + if let __unwrappedValue = cameraDevice { + return bridge.create_std__optional_CameraDevice_(__unwrappedValue) + } else { + return .init() + } + }(), { () -> bridge.std__optional_double_ in if let __unwrappedValue = videoMaximumDuration { return bridge.create_std__optional_double_(__unwrappedValue) } else { @@ -28,14 +34,20 @@ public extension PickerCameraConfig { }()) } - var cameraDevice: CameraDevice { + var cameraDevice: CameraDevice? { @inline(__always) get { - return self.__cameraDevice + return self.__cameraDevice.value } @inline(__always) set { - self.__cameraDevice = newValue + self.__cameraDevice = { () -> bridge.std__optional_CameraDevice_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_CameraDevice_(__unwrappedValue) + } else { + return .init() + } + }() } } diff --git a/nitrogen/generated/ios/swift/Result.swift b/nitrogen/generated/ios/swift/Result.swift index b5c36238..510090e3 100644 --- a/nitrogen/generated/ios/swift/Result.swift +++ b/nitrogen/generated/ios/swift/Result.swift @@ -18,8 +18,8 @@ public extension Result { /** * Create a new instance of `Result`. */ - init(path: String, fileName: String, localIdentifier: String, width: Double, height: Double, mime: String, size: Double, bucketId: Double?, realPath: String?, parentFolderName: String?, creationDate: Double?, type: ResultType, duration: Double?, thumbnail: String?, crop: Bool?) { - self.init(std.string(path), std.string(fileName), std.string(localIdentifier), width, height, std.string(mime), size, { () -> bridge.std__optional_double_ in + init(localIdentifier: String, width: Double, height: Double, mime: String, size: Double, bucketId: Double?, realPath: String?, parentFolderName: String?, creationDate: Double?, crop: Bool?, path: String, type: ResultType, duration: Double?, thumbnail: String?, fileName: String?) { + self.init(std.string(localIdentifier), width, height, std.string(mime), size, { () -> bridge.std__optional_double_ in if let __unwrappedValue = bucketId { return bridge.create_std__optional_double_(__unwrappedValue) } else { @@ -43,7 +43,13 @@ public extension Result { } else { return .init() } - }(), type, { () -> bridge.std__optional_double_ in + }(), { () -> bridge.std__optional_bool_ in + if let __unwrappedValue = crop { + return bridge.create_std__optional_bool_(__unwrappedValue) + } else { + return .init() + } + }(), std.string(path), type, { () -> bridge.std__optional_double_ in if let __unwrappedValue = duration { return bridge.create_std__optional_double_(__unwrappedValue) } else { @@ -55,37 +61,15 @@ public extension Result { } else { return .init() } - }(), { () -> bridge.std__optional_bool_ in - if let __unwrappedValue = crop { - return bridge.create_std__optional_bool_(__unwrappedValue) + }(), { () -> bridge.std__optional_std__string_ in + if let __unwrappedValue = fileName { + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) } else { return .init() } }()) } - var path: String { - @inline(__always) - get { - return String(self.__path) - } - @inline(__always) - set { - self.__path = std.string(newValue) - } - } - - var fileName: String { - @inline(__always) - get { - return String(self.__fileName) - } - @inline(__always) - set { - self.__fileName = std.string(newValue) - } - } - var localIdentifier: String { @inline(__always) get { @@ -221,6 +205,34 @@ public extension Result { } } + var crop: Bool? { + @inline(__always) + get { + return self.__crop.value + } + @inline(__always) + set { + self.__crop = { () -> bridge.std__optional_bool_ in + if let __unwrappedValue = newValue { + return bridge.create_std__optional_bool_(__unwrappedValue) + } else { + return .init() + } + }() + } + } + + var path: String { + @inline(__always) + get { + return String(self.__path) + } + @inline(__always) + set { + self.__path = std.string(newValue) + } + } + var type: ResultType { @inline(__always) get { @@ -272,16 +284,22 @@ public extension Result { } } - var crop: Bool? { + var fileName: String? { @inline(__always) get { - return self.__crop.value + return { () -> String? in + if let __unwrapped = self.__fileName.value { + return String(__unwrapped) + } else { + return nil + } + }() } @inline(__always) set { - self.__crop = { () -> bridge.std__optional_bool_ in + self.__fileName = { () -> bridge.std__optional_std__string_ in if let __unwrappedValue = newValue { - return bridge.create_std__optional_bool_(__unwrappedValue) + return bridge.create_std__optional_std__string_(std.string(__unwrappedValue)) } else { return .init() } diff --git a/nitrogen/generated/shared/c++/CameraResult.hpp b/nitrogen/generated/shared/c++/CameraResult.hpp new file mode 100644 index 00000000..6e542625 --- /dev/null +++ b/nitrogen/generated/shared/c++/CameraResult.hpp @@ -0,0 +1,95 @@ +/// +/// CameraResult.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2024 Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +// Forward declaration of `ResultType` to properly resolve imports. +namespace margelo::nitro::multipleimagepicker { enum class ResultType; } + +#include +#include "ResultType.hpp" +#include + +namespace margelo::nitro::multipleimagepicker { + + /** + * A struct which can be represented as a JavaScript object (CameraResult). + */ + struct CameraResult { + public: + std::string path SWIFT_PRIVATE; + ResultType type SWIFT_PRIVATE; + std::optional width SWIFT_PRIVATE; + std::optional height SWIFT_PRIVATE; + std::optional duration SWIFT_PRIVATE; + std::optional thumbnail SWIFT_PRIVATE; + std::optional fileName SWIFT_PRIVATE; + + public: + explicit CameraResult(std::string path, ResultType type, std::optional width, std::optional height, std::optional duration, std::optional thumbnail, std::optional fileName): path(path), type(type), width(width), height(height), duration(duration), thumbnail(thumbnail), fileName(fileName) {} + }; + +} // namespace margelo::nitro::multipleimagepicker + +namespace margelo::nitro { + + using namespace margelo::nitro::multipleimagepicker; + + // C++ CameraResult <> JS CameraResult (object) + template <> + struct JSIConverter { + static inline CameraResult fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + jsi::Object obj = arg.asObject(runtime); + return CameraResult( + JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "path")), + JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "type")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "width")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "height")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "duration")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "thumbnail")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "fileName")) + ); + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, const CameraResult& arg) { + jsi::Object obj(runtime); + obj.setProperty(runtime, "path", JSIConverter::toJSI(runtime, arg.path)); + obj.setProperty(runtime, "type", JSIConverter::toJSI(runtime, arg.type)); + obj.setProperty(runtime, "width", JSIConverter>::toJSI(runtime, arg.width)); + obj.setProperty(runtime, "height", JSIConverter>::toJSI(runtime, arg.height)); + obj.setProperty(runtime, "duration", JSIConverter>::toJSI(runtime, arg.duration)); + obj.setProperty(runtime, "thumbnail", JSIConverter>::toJSI(runtime, arg.thumbnail)); + obj.setProperty(runtime, "fileName", JSIConverter>::toJSI(runtime, arg.fileName)); + return obj; + } + static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { + if (!value.isObject()) { + return false; + } + jsi::Object obj = value.getObject(runtime); + if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "path"))) return false; + if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "type"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "width"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "height"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "duration"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "thumbnail"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "fileName"))) return false; + return true; + } + }; + +} // namespace margelo::nitro diff --git a/nitrogen/generated/shared/c++/HybridMultipleImagePickerSpec.cpp b/nitrogen/generated/shared/c++/HybridMultipleImagePickerSpec.cpp index c6a90ba2..8ea0c119 100644 --- a/nitrogen/generated/shared/c++/HybridMultipleImagePickerSpec.cpp +++ b/nitrogen/generated/shared/c++/HybridMultipleImagePickerSpec.cpp @@ -17,6 +17,7 @@ namespace margelo::nitro::multipleimagepicker { prototype.registerHybridMethod("openPicker", &HybridMultipleImagePickerSpec::openPicker); prototype.registerHybridMethod("openCrop", &HybridMultipleImagePickerSpec::openCrop); prototype.registerHybridMethod("openPreview", &HybridMultipleImagePickerSpec::openPreview); + prototype.registerHybridMethod("openCamera", &HybridMultipleImagePickerSpec::openCamera); }); } diff --git a/nitrogen/generated/shared/c++/HybridMultipleImagePickerSpec.hpp b/nitrogen/generated/shared/c++/HybridMultipleImagePickerSpec.hpp index 9d5b4f3d..6e86b8b0 100644 --- a/nitrogen/generated/shared/c++/HybridMultipleImagePickerSpec.hpp +++ b/nitrogen/generated/shared/c++/HybridMultipleImagePickerSpec.hpp @@ -25,6 +25,10 @@ namespace margelo::nitro::multipleimagepicker { struct CropResult; } namespace margelo::nitro::multipleimagepicker { struct MediaPreview; } // Forward declaration of `NitroPreviewConfig` to properly resolve imports. namespace margelo::nitro::multipleimagepicker { struct NitroPreviewConfig; } +// Forward declaration of `NitroCameraConfig` to properly resolve imports. +namespace margelo::nitro::multipleimagepicker { struct NitroCameraConfig; } +// Forward declaration of `CameraResult` to properly resolve imports. +namespace margelo::nitro::multipleimagepicker { struct CameraResult; } #include "NitroConfig.hpp" #include @@ -35,6 +39,8 @@ namespace margelo::nitro::multipleimagepicker { struct NitroPreviewConfig; } #include "CropResult.hpp" #include "MediaPreview.hpp" #include "NitroPreviewConfig.hpp" +#include "NitroCameraConfig.hpp" +#include "CameraResult.hpp" namespace margelo::nitro::multipleimagepicker { @@ -70,6 +76,7 @@ namespace margelo::nitro::multipleimagepicker { virtual void openPicker(const NitroConfig& config, const std::function& /* result */)>& resolved, const std::function& rejected) = 0; virtual void openCrop(const std::string& image, const NitroCropConfig& config, const std::function& resolved, const std::function& rejected) = 0; virtual void openPreview(const std::vector& media, double index, const NitroPreviewConfig& config) = 0; + virtual void openCamera(const NitroCameraConfig& config, const std::function& resolved, const std::function& rejected) = 0; protected: // Hybrid Setup diff --git a/nitrogen/generated/shared/c++/NitroCameraConfig.hpp b/nitrogen/generated/shared/c++/NitroCameraConfig.hpp new file mode 100644 index 00000000..7218f5e5 --- /dev/null +++ b/nitrogen/generated/shared/c++/NitroCameraConfig.hpp @@ -0,0 +1,110 @@ +/// +/// NitroCameraConfig.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2024 Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + +// Forward declaration of `MediaType` to properly resolve imports. +namespace margelo::nitro::multipleimagepicker { enum class MediaType; } +// Forward declaration of `Presentation` to properly resolve imports. +namespace margelo::nitro::multipleimagepicker { enum class Presentation; } +// Forward declaration of `Language` to properly resolve imports. +namespace margelo::nitro::multipleimagepicker { enum class Language; } +// Forward declaration of `PickerCropConfig` to properly resolve imports. +namespace margelo::nitro::multipleimagepicker { struct PickerCropConfig; } +// Forward declaration of `CameraDevice` to properly resolve imports. +namespace margelo::nitro::multipleimagepicker { enum class CameraDevice; } + +#include "MediaType.hpp" +#include "Presentation.hpp" +#include "Language.hpp" +#include +#include "PickerCropConfig.hpp" +#include "CameraDevice.hpp" + +namespace margelo::nitro::multipleimagepicker { + + /** + * A struct which can be represented as a JavaScript object (NitroCameraConfig). + */ + struct NitroCameraConfig { + public: + MediaType mediaType SWIFT_PRIVATE; + Presentation presentation SWIFT_PRIVATE; + Language language SWIFT_PRIVATE; + std::optional crop SWIFT_PRIVATE; + std::optional isSaveSystemAlbum SWIFT_PRIVATE; + std::optional color SWIFT_PRIVATE; + std::optional cameraDevice SWIFT_PRIVATE; + std::optional videoMaximumDuration SWIFT_PRIVATE; + + public: + explicit NitroCameraConfig(MediaType mediaType, Presentation presentation, Language language, std::optional crop, std::optional isSaveSystemAlbum, std::optional color, std::optional cameraDevice, std::optional videoMaximumDuration): mediaType(mediaType), presentation(presentation), language(language), crop(crop), isSaveSystemAlbum(isSaveSystemAlbum), color(color), cameraDevice(cameraDevice), videoMaximumDuration(videoMaximumDuration) {} + }; + +} // namespace margelo::nitro::multipleimagepicker + +namespace margelo::nitro { + + using namespace margelo::nitro::multipleimagepicker; + + // C++ NitroCameraConfig <> JS NitroCameraConfig (object) + template <> + struct JSIConverter { + static inline NitroCameraConfig fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { + jsi::Object obj = arg.asObject(runtime); + return NitroCameraConfig( + JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "mediaType")), + JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "presentation")), + JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "language")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "crop")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "isSaveSystemAlbum")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "color")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "cameraDevice")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "videoMaximumDuration")) + ); + } + static inline jsi::Value toJSI(jsi::Runtime& runtime, const NitroCameraConfig& arg) { + jsi::Object obj(runtime); + obj.setProperty(runtime, "mediaType", JSIConverter::toJSI(runtime, arg.mediaType)); + obj.setProperty(runtime, "presentation", JSIConverter::toJSI(runtime, arg.presentation)); + obj.setProperty(runtime, "language", JSIConverter::toJSI(runtime, arg.language)); + obj.setProperty(runtime, "crop", JSIConverter>::toJSI(runtime, arg.crop)); + obj.setProperty(runtime, "isSaveSystemAlbum", JSIConverter>::toJSI(runtime, arg.isSaveSystemAlbum)); + obj.setProperty(runtime, "color", JSIConverter>::toJSI(runtime, arg.color)); + obj.setProperty(runtime, "cameraDevice", JSIConverter>::toJSI(runtime, arg.cameraDevice)); + obj.setProperty(runtime, "videoMaximumDuration", JSIConverter>::toJSI(runtime, arg.videoMaximumDuration)); + return obj; + } + static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { + if (!value.isObject()) { + return false; + } + jsi::Object obj = value.getObject(runtime); + if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "mediaType"))) return false; + if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "presentation"))) return false; + if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "language"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "crop"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "isSaveSystemAlbum"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "color"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "cameraDevice"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "videoMaximumDuration"))) return false; + return true; + } + }; + +} // namespace margelo::nitro diff --git a/nitrogen/generated/shared/c++/NitroPreviewConfig.hpp b/nitrogen/generated/shared/c++/NitroPreviewConfig.hpp index 772e36e3..1d4d3096 100644 --- a/nitrogen/generated/shared/c++/NitroPreviewConfig.hpp +++ b/nitrogen/generated/shared/c++/NitroPreviewConfig.hpp @@ -22,6 +22,7 @@ namespace margelo::nitro::multipleimagepicker { enum class Language; } #include "Language.hpp" +#include namespace margelo::nitro::multipleimagepicker { @@ -31,9 +32,10 @@ namespace margelo::nitro::multipleimagepicker { struct NitroPreviewConfig { public: Language language SWIFT_PRIVATE; + std::optional videoAutoPlay SWIFT_PRIVATE; public: - explicit NitroPreviewConfig(Language language): language(language) {} + explicit NitroPreviewConfig(Language language, std::optional videoAutoPlay): language(language), videoAutoPlay(videoAutoPlay) {} }; } // namespace margelo::nitro::multipleimagepicker @@ -48,12 +50,14 @@ namespace margelo::nitro { static inline NitroPreviewConfig fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { jsi::Object obj = arg.asObject(runtime); return NitroPreviewConfig( - JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "language")) + JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "language")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "videoAutoPlay")) ); } static inline jsi::Value toJSI(jsi::Runtime& runtime, const NitroPreviewConfig& arg) { jsi::Object obj(runtime); obj.setProperty(runtime, "language", JSIConverter::toJSI(runtime, arg.language)); + obj.setProperty(runtime, "videoAutoPlay", JSIConverter>::toJSI(runtime, arg.videoAutoPlay)); return obj; } static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { @@ -62,6 +66,7 @@ namespace margelo::nitro { } jsi::Object obj = value.getObject(runtime); if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "language"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "videoAutoPlay"))) return false; return true; } }; diff --git a/nitrogen/generated/shared/c++/PickerCameraConfig.hpp b/nitrogen/generated/shared/c++/PickerCameraConfig.hpp index 649b447f..26d11cf6 100644 --- a/nitrogen/generated/shared/c++/PickerCameraConfig.hpp +++ b/nitrogen/generated/shared/c++/PickerCameraConfig.hpp @@ -21,8 +21,8 @@ // Forward declaration of `CameraDevice` to properly resolve imports. namespace margelo::nitro::multipleimagepicker { enum class CameraDevice; } -#include "CameraDevice.hpp" #include +#include "CameraDevice.hpp" namespace margelo::nitro::multipleimagepicker { @@ -31,11 +31,11 @@ namespace margelo::nitro::multipleimagepicker { */ struct PickerCameraConfig { public: - CameraDevice cameraDevice SWIFT_PRIVATE; + std::optional cameraDevice SWIFT_PRIVATE; std::optional videoMaximumDuration SWIFT_PRIVATE; public: - explicit PickerCameraConfig(CameraDevice cameraDevice, std::optional videoMaximumDuration): cameraDevice(cameraDevice), videoMaximumDuration(videoMaximumDuration) {} + explicit PickerCameraConfig(std::optional cameraDevice, std::optional videoMaximumDuration): cameraDevice(cameraDevice), videoMaximumDuration(videoMaximumDuration) {} }; } // namespace margelo::nitro::multipleimagepicker @@ -50,13 +50,13 @@ namespace margelo::nitro { static inline PickerCameraConfig fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { jsi::Object obj = arg.asObject(runtime); return PickerCameraConfig( - JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "cameraDevice")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "cameraDevice")), JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "videoMaximumDuration")) ); } static inline jsi::Value toJSI(jsi::Runtime& runtime, const PickerCameraConfig& arg) { jsi::Object obj(runtime); - obj.setProperty(runtime, "cameraDevice", JSIConverter::toJSI(runtime, arg.cameraDevice)); + obj.setProperty(runtime, "cameraDevice", JSIConverter>::toJSI(runtime, arg.cameraDevice)); obj.setProperty(runtime, "videoMaximumDuration", JSIConverter>::toJSI(runtime, arg.videoMaximumDuration)); return obj; } @@ -65,7 +65,7 @@ namespace margelo::nitro { return false; } jsi::Object obj = value.getObject(runtime); - if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "cameraDevice"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "cameraDevice"))) return false; if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "videoMaximumDuration"))) return false; return true; } diff --git a/nitrogen/generated/shared/c++/Result.hpp b/nitrogen/generated/shared/c++/Result.hpp index 7abaf9ac..a4f82236 100644 --- a/nitrogen/generated/shared/c++/Result.hpp +++ b/nitrogen/generated/shared/c++/Result.hpp @@ -32,8 +32,6 @@ namespace margelo::nitro::multipleimagepicker { */ struct Result { public: - std::string path SWIFT_PRIVATE; - std::string fileName SWIFT_PRIVATE; std::string localIdentifier SWIFT_PRIVATE; double width SWIFT_PRIVATE; double height SWIFT_PRIVATE; @@ -43,13 +41,15 @@ namespace margelo::nitro::multipleimagepicker { std::optional realPath SWIFT_PRIVATE; std::optional parentFolderName SWIFT_PRIVATE; std::optional creationDate SWIFT_PRIVATE; + std::optional crop SWIFT_PRIVATE; + std::string path SWIFT_PRIVATE; ResultType type SWIFT_PRIVATE; std::optional duration SWIFT_PRIVATE; std::optional thumbnail SWIFT_PRIVATE; - std::optional crop SWIFT_PRIVATE; + std::optional fileName SWIFT_PRIVATE; public: - explicit Result(std::string path, std::string fileName, std::string localIdentifier, double width, double height, std::string mime, double size, std::optional bucketId, std::optional realPath, std::optional parentFolderName, std::optional creationDate, ResultType type, std::optional duration, std::optional thumbnail, std::optional crop): path(path), fileName(fileName), localIdentifier(localIdentifier), width(width), height(height), mime(mime), size(size), bucketId(bucketId), realPath(realPath), parentFolderName(parentFolderName), creationDate(creationDate), type(type), duration(duration), thumbnail(thumbnail), crop(crop) {} + explicit Result(std::string localIdentifier, double width, double height, std::string mime, double size, std::optional bucketId, std::optional realPath, std::optional parentFolderName, std::optional creationDate, std::optional crop, std::string path, ResultType type, std::optional duration, std::optional thumbnail, std::optional fileName): localIdentifier(localIdentifier), width(width), height(height), mime(mime), size(size), bucketId(bucketId), realPath(realPath), parentFolderName(parentFolderName), creationDate(creationDate), crop(crop), path(path), type(type), duration(duration), thumbnail(thumbnail), fileName(fileName) {} }; } // namespace margelo::nitro::multipleimagepicker @@ -64,8 +64,6 @@ namespace margelo::nitro { static inline Result fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) { jsi::Object obj = arg.asObject(runtime); return Result( - JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "path")), - JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "fileName")), JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "localIdentifier")), JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "width")), JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "height")), @@ -75,16 +73,16 @@ namespace margelo::nitro { JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "realPath")), JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "parentFolderName")), JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "creationDate")), + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "crop")), + JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "path")), JSIConverter::fromJSI(runtime, obj.getProperty(runtime, "type")), JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "duration")), JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "thumbnail")), - JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "crop")) + JSIConverter>::fromJSI(runtime, obj.getProperty(runtime, "fileName")) ); } static inline jsi::Value toJSI(jsi::Runtime& runtime, const Result& arg) { jsi::Object obj(runtime); - obj.setProperty(runtime, "path", JSIConverter::toJSI(runtime, arg.path)); - obj.setProperty(runtime, "fileName", JSIConverter::toJSI(runtime, arg.fileName)); obj.setProperty(runtime, "localIdentifier", JSIConverter::toJSI(runtime, arg.localIdentifier)); obj.setProperty(runtime, "width", JSIConverter::toJSI(runtime, arg.width)); obj.setProperty(runtime, "height", JSIConverter::toJSI(runtime, arg.height)); @@ -94,10 +92,12 @@ namespace margelo::nitro { obj.setProperty(runtime, "realPath", JSIConverter>::toJSI(runtime, arg.realPath)); obj.setProperty(runtime, "parentFolderName", JSIConverter>::toJSI(runtime, arg.parentFolderName)); obj.setProperty(runtime, "creationDate", JSIConverter>::toJSI(runtime, arg.creationDate)); + obj.setProperty(runtime, "crop", JSIConverter>::toJSI(runtime, arg.crop)); + obj.setProperty(runtime, "path", JSIConverter::toJSI(runtime, arg.path)); obj.setProperty(runtime, "type", JSIConverter::toJSI(runtime, arg.type)); obj.setProperty(runtime, "duration", JSIConverter>::toJSI(runtime, arg.duration)); obj.setProperty(runtime, "thumbnail", JSIConverter>::toJSI(runtime, arg.thumbnail)); - obj.setProperty(runtime, "crop", JSIConverter>::toJSI(runtime, arg.crop)); + obj.setProperty(runtime, "fileName", JSIConverter>::toJSI(runtime, arg.fileName)); return obj; } static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) { @@ -105,8 +105,6 @@ namespace margelo::nitro { return false; } jsi::Object obj = value.getObject(runtime); - if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "path"))) return false; - if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "fileName"))) return false; if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "localIdentifier"))) return false; if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "width"))) return false; if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "height"))) return false; @@ -116,10 +114,12 @@ namespace margelo::nitro { if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "realPath"))) return false; if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "parentFolderName"))) return false; if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "creationDate"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "crop"))) return false; + if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "path"))) return false; if (!JSIConverter::canConvert(runtime, obj.getProperty(runtime, "type"))) return false; if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "duration"))) return false; if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "thumbnail"))) return false; - if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "crop"))) return false; + if (!JSIConverter>::canConvert(runtime, obj.getProperty(runtime, "fileName"))) return false; return true; } }; diff --git a/src/index.ts b/src/index.ts index 30bfbe6d..95c7f258 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,12 +14,15 @@ import { CropResult, CropConfig, NitroCropConfig, - CropRatio, PreviewConfig, NitroPreviewConfig, MediaPreview, + CameraConfig, + NitroCameraConfig, + CameraResult, + Language, } from './types' -import { CropError } from './types/error' +import { CropError, CameraError } from './types/error' const Picker = NitroModules.createHybridObject( 'MultipleImagePicker' @@ -42,11 +45,13 @@ export async function openPicker( config.theme = theme } - if (config?.language && !LANGUAGES.includes(config.language)) { - config.language = 'system' + config.language = validateLanguage(config.language) + + if (typeof config.crop === 'boolean') { + config.crop = config.crop ? { ratio: [] } : undefined } - if (config.crop) config.crop.ratio = config.crop.ratio ?? [] + if (config.crop) config.crop.ratio = config.crop?.ratio ?? [] return Picker.openPicker( config, @@ -72,9 +77,7 @@ export async function openCropper( ...config, } as NitroCropConfig - if (config?.language && !LANGUAGES.includes(config.language)) { - config.language = 'system' - } + cropConfig.language = validateLanguage(cropConfig.language) return Picker.openCrop( image, @@ -92,10 +95,11 @@ export async function openCropper( export function openPreview( media: MediaPreview[] | Result[], index: number = 0, - conf: PreviewConfig + conf?: PreviewConfig ): void { const config: PreviewConfig = { - language: conf.language ?? 'system', + language: conf?.language ?? 'system', + videoAutoPlay: true, ...conf, } @@ -114,14 +118,56 @@ export function openPreview( ) } +export async function openCamera(config?: CameraConfig): Promise { + return new Promise((resolved, rejected) => { + const cameraConfig = { + cameraDevice: 'back', + presentation: 'fullScreenModal', + language: 'system', + mediaType: 'all', + allowLocation: true, + isSaveSystemAlbum: false, + ...config, + } as NitroCameraConfig + + cameraConfig.color = processColor(cameraConfig.color ?? primaryColor) as any + + cameraConfig.language = validateLanguage(cameraConfig.language) + + if (typeof cameraConfig.crop === 'boolean') { + cameraConfig.crop = cameraConfig.crop ? { ratio: [] } : undefined + } + + if (cameraConfig.crop && !cameraConfig.crop?.ratio) + cameraConfig.crop.ratio = [] + + return Picker.openCamera( + cameraConfig, + (result: CameraResult) => { + resolved(result) + }, + (error: CameraError) => { + rejected(error) + } + ) + }) +} + const DEFAULT_COUNT = 20 -export const DEFAULT_RATIO: CropRatio[] = [] +const validateLanguage = (language?: Language): Language => { + if (!language || !LANGUAGES.includes(language)) { + return 'system' + } + return language +} + +const primaryColor = '#2979ff' export const defaultOptions: Config = { maxSelect: DEFAULT_COUNT, maxVideo: DEFAULT_COUNT, - primaryColor: '#FB9300', + primaryColor, backgroundDark: '#2f2f2f', allowedLimit: true, numberOfColumn: 3, @@ -136,7 +182,7 @@ export const defaultOptions: Config = { isHiddenOriginalButton: false, allowSwipeToSelect: true, camera: { - cameraDevice: 'front', + cameraDevice: 'back', videoMaximumDuration: 60, }, } diff --git a/src/specs/MultipleImagePicker.nitro.ts b/src/specs/MultipleImagePicker.nitro.ts index 11bf82c7..190f7e32 100644 --- a/src/specs/MultipleImagePicker.nitro.ts +++ b/src/specs/MultipleImagePicker.nitro.ts @@ -1,7 +1,9 @@ import { type HybridObject } from 'react-native-nitro-modules' import { + CameraResult, CropResult, MediaPreview, + NitroCameraConfig, NitroConfig, NitroCropConfig, NitroPreviewConfig, @@ -28,4 +30,10 @@ export interface MultipleImagePicker index: number, config: NitroPreviewConfig ): void + + openCamera( + config: NitroCameraConfig, + resolved: (result: CameraResult) => void, + rejected: (reject: number) => void + ): void } diff --git a/src/types/camera.ts b/src/types/camera.ts index d2223774..c599bd27 100644 --- a/src/types/camera.ts +++ b/src/types/camera.ts @@ -1,32 +1,138 @@ -import { MediaType } from './config' +import { ColorValue } from 'react-native' +import { Language, MediaType, Presentation } from './config' +import { PickerCropConfig, CropRatio } from './crop' +import { BaseResult } from './result' export type CameraDevice = 'front' | 'back' export type PickerCameraConfig = { - cameraDevice: CameraDevice + /** + * Type of camera + * @typedef {'front' | 'back'} CameraDevice + */ + cameraDevice?: CameraDevice + + /** + * Maximum duration of video + * @type {number} + */ videoMaximumDuration?: number } export interface NitroCameraConfig extends PickerCameraConfig { mediaType: MediaType -} -export interface CameraConfig - extends Omit { + presentation: Presentation + + language: Language + + crop?: PickerCropConfig + /** - * Type of camera - * @typedef {'front' | 'back'} CameraDevice + * Save image to system album + * @type {boolean} */ - cameraDevice: CameraDevice + isSaveSystemAlbum?: boolean + color?: number +} + +export interface CameraConfig + extends Omit< + NitroCameraConfig, + 'mediaType' | 'language' | 'presentation' | 'crop' | 'color' + > { /** * Type of media to be displayed * @typedef {'video' | 'image' | 'all'} MediaType */ - mediaType: MediaType + mediaType?: MediaType /** - * Maximum duration of video - * @type {number} + * Modal presentation style for the picker. + * - 'fullScreenModal': Opens picker in full screen + * - 'formSheet': Opens picker in a form sheet style + * + * @platform ios + * @default 'fullScreenModal' + * @type {Presentation} + * @example + * ```ts + * presentation: 'formSheet' + * ``` + */ + presentation?: Presentation + + /** + * Language options for the picker. + * @description + * - 'system': 🌐 System default + * - 'zh-Hans': 🇨🇳 Simplified Chinese + * - 'zh-Hant': 🇹🇼 Traditional Chinese + * - 'ja': 🇯🇵 Japanese + * - 'ko': 🇰🇷 Korean + * - 'en': 🇬🇧 English + * - 'th': 🇹🇭 Thai + * - 'id': 🇮🇩 Indonesian + * - 'vi': 🇻🇳 Vietnamese (My Country) + * - 'ru': 🇷🇺 Russian + * - 'de': 🇩🇪 German + * - 'fr': 🇫🇷 French + * - 'ar': 🇸🇦 Arabic + */ + language?: Language + + crop?: + | boolean + | (Omit & { + /** + * Array of aspect ratios for image cropping. The ratios will be inserted after the default ratios (Original and Square). + * Android: Maximum: 4 items + * + * @platform ios, Android + * + * @property {Array} ratio - Array of custom aspect ratios + * @property {string} [ratio[].title] - Optional display title for the ratio (e.g., "16:9"). If not provided, will use "width/height" + * @property {number} ratio[].width - Width value for aspect ratio + * @property {number} ratio[].height - Height value for aspect ratio + * + * @example + * ```ts + * ratio: [ + * { title: "Instagram", width: 1, height: 1 }, + * { title: "Twitter", width: 16, height: 9 }, + * { width: 12, height: 11 } + * ] + * ``` + */ + ratio?: CropRatio[] + + /** + * Camera configuration + * @type {CameraConfig} + */ + }) + + /** + * Primary color for the picker UI elements. + * Accepts various color formats: + * - Hex strings: '#RGB', '#RGBA', '#RRGGBB', '#RRGGBBAA' + * - RGB/RGBA strings: 'rgb(255, 0, 0)', 'rgba(255, 0, 0, 0.5)' + * - Named colors: 'red', 'blue', etc. + * - Numbers for RGB values + * + * @platform ios, android + * @type {ColorValue} + * @example + * ```ts + * primaryColor: '#FF0000' + * primaryColor: 'rgb(255, 0, 0)' + * primaryColor: 'red' + * ``` */ + color?: ColorValue +} + +export interface CameraResult extends BaseResult { + // } diff --git a/src/types/config.ts b/src/types/config.ts index 96a81e61..cdafccef 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -18,5 +18,3 @@ export type Language = | 'ar' export type MediaType = 'video' | 'image' | 'all' - -export type CropRatio = { title?: string; width: number; height: number } diff --git a/src/types/crop.ts b/src/types/crop.ts index 5670af06..e9421f5d 100644 --- a/src/types/crop.ts +++ b/src/types/crop.ts @@ -1,4 +1,6 @@ -import { CropRatio, Language, Presentation } from './config' +import { Language, Presentation } from './config' + +export type CropRatio = { title?: string; width: number; height: number } /** * Configuration for image cropping diff --git a/src/types/error.ts b/src/types/error.ts index 02e570c3..db7cb1cf 100644 --- a/src/types/error.ts +++ b/src/types/error.ts @@ -5,3 +5,7 @@ export enum MultipleImagePickerError { export enum CropError { INVALID_IMAGE = 0, } + +export enum CameraError { + INVALID_OUTPUT_FILE = 1, +} diff --git a/src/types/index.ts b/src/types/index.ts index 218313bc..065ef199 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,5 +1,3 @@ -// export * from './default'; -// export * from './error' export * from './result' export * from './config' export * from './crop' diff --git a/src/types/picker.ts b/src/types/picker.ts index 514d2dae..154972c7 100644 --- a/src/types/picker.ts +++ b/src/types/picker.ts @@ -1,7 +1,7 @@ import { ColorValue } from 'react-native' -import { CropRatio, Language, MediaType, Presentation, Theme } from './config' +import { Language, MediaType, Presentation, Theme } from './config' import { Result } from './result' -import { PickerCropConfig } from './crop' +import { PickerCropConfig, CropRatio } from './crop' import { PickerCameraConfig } from './camera' export type SelectBoxStyle = 'number' | 'tick' @@ -217,6 +217,7 @@ export interface NitroConfig { camera?: PickerCameraConfig } +// CONFIG TYPE export interface Config extends Omit< NitroConfig, @@ -345,34 +346,36 @@ export interface Config * @interface PickerCropConfig */ - crop?: Omit & { - /** - * Array of aspect ratios for image cropping. The ratios will be inserted after the default ratios (Original and Square). - * Android: Maximum: 4 items - * - * @platform ios, Android - * - * @property {Array} ratio - Array of custom aspect ratios - * @property {string} [ratio[].title] - Optional display title for the ratio (e.g., "16:9"). If not provided, will use "width/height" - * @property {number} ratio[].width - Width value for aspect ratio - * @property {number} ratio[].height - Height value for aspect ratio - * - * @example - * ```ts - * ratio: [ - * { title: "Instagram", width: 1, height: 1 }, - * { title: "Twitter", width: 16, height: 9 }, - * { width: 12, height: 11 } - * ] - * ``` - */ - ratio?: CropRatio[] - - /** - * Camera configuration - * @type {CameraConfig} - */ - } + crop?: + | boolean + | (Omit & { + /** + * Array of aspect ratios for image cropping. The ratios will be inserted after the default ratios (Original and Square). + * Android: Maximum: 4 items + * + * @platform ios, Android + * + * @property {Array} ratio - Array of custom aspect ratios + * @property {string} [ratio[].title] - Optional display title for the ratio (e.g., "16:9"). If not provided, will use "width/height" + * @property {number} ratio[].width - Width value for aspect ratio + * @property {number} ratio[].height - Height value for aspect ratio + * + * @example + * ```ts + * ratio: [ + * { title: "Instagram", width: 1, height: 1 }, + * { title: "Twitter", width: 16, height: 9 }, + * { width: 12, height: 11 } + * ] + * ``` + */ + ratio?: CropRatio[] + + /** + * Camera configuration + * @type {CameraConfig} + */ + }) camera?: PickerCameraConfig } diff --git a/src/types/preview.ts b/src/types/preview.ts index e61c994a..b18186af 100644 --- a/src/types/preview.ts +++ b/src/types/preview.ts @@ -4,6 +4,13 @@ import { ResultType } from './result' // PREVIEW export type NitroPreviewConfig = { language: Language + + /** + * Auto play video when open preview. + * + * @platform iOS, Android + */ + videoAutoPlay?: boolean } export interface PreviewConfig diff --git a/src/types/result.ts b/src/types/result.ts index 59e875f0..2300f87b 100644 --- a/src/types/result.ts +++ b/src/types/result.ts @@ -1,19 +1,153 @@ +/** + * Represents the type of media content + * @example + * const type: ResultType = 'image' + * const type: ResultType = 'video' + */ export type ResultType = 'image' | 'video' -export interface Result { +/** + * Base interface containing common properties for media results. + * Used as a foundation for more specific result types. + * + * @example + * const baseResult: BaseResult = { + * path: '/path/to/media/file.jpg', + * type: 'image', + * width: 1920, + * height: 1080, + * fileName: 'file.jpg' + * } + */ +export interface BaseResult { + /** + * File path of the media asset + * Can be relative or absolute depending on context + */ path: string - fileName: string + + /** + * Type of media content + * Used to determine how the asset should be handled + */ + type: ResultType + + /** + * Width of the media in pixels + * Optional in base result as it may not be immediately available + */ + width?: number + + /** + * Height of the media in pixels + * Optional in base result as it may not be immediately available + */ + height?: number + + /** + * Duration of the media in seconds + * Only applicable for video content + * @example 120.5 // 2 minutes and 30 seconds + */ + duration?: number + + /** + * Path to a thumbnail image representation + * Useful for previews and grid displays + * Can be a local path or URL depending on implementation + */ + thumbnail?: string + + /** + * Original filename of the media asset + * Includes the file extension + * @example "IMG_20240301_123456.jpg" + */ + fileName?: string +} + +/** + * Extended result interface with complete metadata and file information. + * Used for fully processed media assets where all properties are known. + * + * @extends BaseResult + * + * @example + * const result: Result = { + * path: '/path/to/media/file.jpg', + * type: 'image', + * width: 1920, + * height: 1080, + * fileName: 'file.jpg', + * localIdentifier: 'unique-id-123', + * mime: 'image/jpeg', + * size: 1024000, + * creationDate: 1709312436000 + * } + */ +export interface Result extends BaseResult { + /** + * Unique identifier for the media asset + * Used for local database tracking and reference + * Format may vary depending on platform/implementation + */ localIdentifier: string + + /** + * Width of the media in pixels + * Required in Result interface as it should be known after processing + */ width: number + + /** + * Height of the media in pixels + * Required in Result interface as it should be known after processing + */ height: number + + /** + * MIME type of the media file + * @example "image/jpeg", "video/mp4" + */ mime: string + + /** + * File size in bytes + * @example 1024000 // 1MB + */ size: number + + /** + * Optional identifier for storage bucket + * Used when assets are stored in cloud storage systems + * @platform android + */ bucketId?: number + + /** + * Actual file path on the system + * May differ from the `path` property in cases where + * the file is stored in a different location than referenced + * @platform android + */ realPath?: string + + /** + * Name of the containing folder + * Useful for organization and grouping + * @platform android + */ parentFolderName?: string + + /** + * Unix timestamp in milliseconds of when the media was created + * @example 1709312436000 // March 1, 2024 12:34:56 PM + */ creationDate?: number - type: ResultType - duration?: number - thumbnail?: string + + /** + * Indicates if the media has been cropped from its original dimensions + * Used to track image modifications + */ crop?: boolean }