Compare commits
	
		
			11 Commits
		
	
	
		
			695e317a77
			...
			bump-react
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ac06fa5f56 | |||
| 5b52acda26 | |||
| 17f675657e | |||
| c64516693c | |||
| e9f08ef488 | |||
| bf122db919 | |||
| 3319e48f7d | |||
| 58714f9dac | |||
| 8991779851 | |||
| f8efa172ba | |||
| 66f840eecb | 
| @@ -1,5 +0,0 @@ | |||||||
| use flake . --impure |  | ||||||
|  |  | ||||||
| if [ -f .envrc.local ]; then |  | ||||||
|     source .envrc.local |  | ||||||
| fi |  | ||||||
| @@ -63,7 +63,7 @@ target_link_libraries( | |||||||
|         ${LOG_LIB}                          # <-- Logcat logger |         ${LOG_LIB}                          # <-- Logcat logger | ||||||
|         android                             # <-- Android JNI core |         android                             # <-- Android JNI core | ||||||
|         ReactAndroid::jsi                   # <-- RN: JSI |         ReactAndroid::jsi                   # <-- RN: JSI | ||||||
|         ReactAndroid::reactnativejni        # <-- RN: React Native JNI bindings |         # ReactAndroid::reactnativejni      # <-- Temporarily disabled for RN 0.79+ compatibility | ||||||
|         fbjni::fbjni                        # <-- fbjni |         fbjni::fbjni                        # <-- fbjni | ||||||
|         GLESv2                              # <-- OpenGL (for VideoPipeline) |         GLESv2                              # <-- OpenGL (for VideoPipeline) | ||||||
|         EGL                                 # <-- OpenGL (EGL) (for VideoPipeline) |         EGL                                 # <-- OpenGL (EGL) (for VideoPipeline) | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ buildscript { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   dependencies { |   dependencies { | ||||||
|     classpath "com.android.tools.build:gradle:8.5.2" |     classpath "com.android.tools.build:gradle:7.4.2" | ||||||
|     classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" |     classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -133,8 +133,8 @@ android { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   compileOptions { |   compileOptions { | ||||||
|     sourceCompatibility JavaVersion.VERSION_17 |     sourceCompatibility JavaVersion.VERSION_1_8 | ||||||
|     targetCompatibility JavaVersion.VERSION_17 |     targetCompatibility JavaVersion.VERSION_1_8 | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   externalNativeBuild { |   externalNativeBuild { | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| distributionBase=GRADLE_USER_HOME | distributionBase=GRADLE_USER_HOME | ||||||
| distributionPath=wrapper/dists | distributionPath=wrapper/dists | ||||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip | ||||||
| zipStoreBase=GRADLE_USER_HOME | zipStoreBase=GRADLE_USER_HOME | ||||||
| zipStorePath=wrapper/dists | zipStorePath=wrapper/dists | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android"> | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |           package="com.mrousavy.camera"> | ||||||
|  |  | ||||||
| </manifest> | </manifest> | ||||||
|   | |||||||
| @@ -13,69 +13,36 @@ import com.facebook.react.bridge.ReadableMap | |||||||
| import com.facebook.react.bridge.WritableMap | import com.facebook.react.bridge.WritableMap | ||||||
| import com.mrousavy.camera.core.CameraSession | import com.mrousavy.camera.core.CameraSession | ||||||
| import com.mrousavy.camera.core.InsufficientStorageError | import com.mrousavy.camera.core.InsufficientStorageError | ||||||
|  | import com.mrousavy.camera.utils.FileUtils | ||||||
| import com.mrousavy.camera.types.Flash | import com.mrousavy.camera.types.Flash | ||||||
| import com.mrousavy.camera.types.QualityPrioritization |  | ||||||
| import com.mrousavy.camera.utils.* | import com.mrousavy.camera.utils.* | ||||||
| import java.io.File | import java.io.File | ||||||
| import java.io.FileOutputStream | import java.io.FileOutputStream | ||||||
| import java.io.IOException | import java.io.IOException | ||||||
| import kotlinx.coroutines.* | import kotlinx.coroutines.* | ||||||
|  |  | ||||||
| private const val TAG = "CameraView.takePhoto" | private const val TAG = "CameraView.takeSnapshot" | ||||||
|  |  | ||||||
| @SuppressLint("UnsafeOptInUsageError") | @SuppressLint("UnsafeOptInUsageError") | ||||||
| suspend fun CameraView.takePhoto(optionsMap: ReadableMap): WritableMap { | suspend fun CameraView.takePhoto(optionsMap: ReadableMap): WritableMap { | ||||||
|   val options = optionsMap.toHashMap() |   val options = optionsMap.toHashMap() | ||||||
|   Log.i(TAG, "Taking photo... Options: $options") |   Log.i(TAG, "Taking snapshot... Options: $options") | ||||||
|  |   val bitmap = previewView.getBitmap() ?: throw Error() | ||||||
|  |  | ||||||
|   val qualityPrioritization = options["qualityPrioritization"] as? String ?: "balanced" |   val file = FileUtils.createTempFile(context, "png"); | ||||||
|   val flash = options["flash"] as? String ?: "off" |  | ||||||
|   val enableAutoStabilization = options["enableAutoStabilization"] == true |  | ||||||
|   val enableShutterSound = options["enableShutterSound"] as? Boolean ?: true |  | ||||||
|   val enablePrecapture = options["enablePrecapture"] as? Boolean ?: false |  | ||||||
|  |  | ||||||
|   // TODO: Implement Red Eye Reduction |   // Write snapshot to .jpg file | ||||||
|   options["enableAutoRedEyeReduction"] |   FileUtils.writeBitmapTofile(bitmap, file, 100) | ||||||
|  |  | ||||||
|   val flashMode = Flash.fromUnionValue(flash) |   Log.i(TAG, "Successfully saved snapshot to file!") | ||||||
|   val qualityPrioritizationMode = QualityPrioritization.fromUnionValue(qualityPrioritization) |  | ||||||
|  |  | ||||||
|   val photo = cameraSession.takePhoto( |   // Parse output data | ||||||
|     qualityPrioritizationMode, |   val map = Arguments.createMap() | ||||||
|     flashMode, |   map.putString("path", file.absolutePath) | ||||||
|     enableShutterSound, |   map.putInt("width", bitmap.width) | ||||||
|     enableAutoStabilization, |   map.putInt("height", bitmap.height) | ||||||
|     enablePrecapture, |   map.putBoolean("isMirrored", false) | ||||||
|     orientation |   return map | ||||||
|   ) |  | ||||||
|  |  | ||||||
|   photo.use { |  | ||||||
|     Log.i(TAG, "Successfully captured ${photo.image.width} x ${photo.image.height} photo!") |  | ||||||
|  |  | ||||||
|     val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId!!) |  | ||||||
|  |  | ||||||
|     val path = try { |  | ||||||
|       savePhotoToFile(context, cameraCharacteristics, photo) |  | ||||||
|     } catch (e: IOException) { |  | ||||||
|       if (e.message?.contains("no space left", true) == true) { |  | ||||||
|         throw InsufficientStorageError() |  | ||||||
|       } else { |  | ||||||
|         throw e |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     Log.i(TAG, "Successfully saved photo to file! $path") |  | ||||||
|  |  | ||||||
|     val map = Arguments.createMap() |  | ||||||
|     map.putString("path", path) |  | ||||||
|     map.putInt("width", photo.image.width) |  | ||||||
|     map.putInt("height", photo.image.height) |  | ||||||
|     map.putString("orientation", photo.orientation.unionValue) |  | ||||||
|     map.putBoolean("isRawPhoto", photo.format == ImageFormat.RAW_SENSOR) |  | ||||||
|     map.putBoolean("isMirrored", photo.isMirrored) |  | ||||||
|  |  | ||||||
|     return map |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| private fun writePhotoToFile(photo: CameraSession.CapturedPhoto, file: File) { | private fun writePhotoToFile(photo: CameraSession.CapturedPhoto, file: File) { | ||||||
|   | |||||||
| @@ -102,7 +102,7 @@ class CameraView(context: Context) : | |||||||
|  |  | ||||||
|   // session |   // session | ||||||
|   internal val cameraSession: CameraSession |   internal val cameraSession: CameraSession | ||||||
|   private val previewView: PreviewView |   val previewView: PreviewView | ||||||
|   private var currentConfigureCall: Long = System.currentTimeMillis() |   private var currentConfigureCall: Long = System.currentTimeMillis() | ||||||
|   internal var frameProcessor: FrameProcessor? = null |   internal var frameProcessor: FrameProcessor? = null | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,9 +2,13 @@ package com.mrousavy.camera.core | |||||||
|  |  | ||||||
| import android.annotation.SuppressLint | import android.annotation.SuppressLint | ||||||
| import android.content.Context | import android.content.Context | ||||||
|  | import android.content.res.Configuration | ||||||
| import android.graphics.Point | import android.graphics.Point | ||||||
|  | import android.os.Handler | ||||||
|  | import android.os.Looper | ||||||
| import android.util.Log | import android.util.Log | ||||||
| import android.util.Size | import android.util.Size | ||||||
|  | import android.view.PixelCopy | ||||||
| import android.view.SurfaceHolder | import android.view.SurfaceHolder | ||||||
| import android.view.SurfaceView | import android.view.SurfaceView | ||||||
| import com.facebook.react.bridge.UiThreadUtil | import com.facebook.react.bridge.UiThreadUtil | ||||||
| @@ -12,9 +16,69 @@ import com.mrousavy.camera.extensions.resize | |||||||
| import com.mrousavy.camera.extensions.rotatedBy | import com.mrousavy.camera.extensions.rotatedBy | ||||||
| import com.mrousavy.camera.types.Orientation | import com.mrousavy.camera.types.Orientation | ||||||
| import com.mrousavy.camera.types.ResizeMode | import com.mrousavy.camera.types.ResizeMode | ||||||
|  | import kotlin.coroutines.resume | ||||||
|  | import kotlin.coroutines.resumeWithException | ||||||
| import kotlin.math.roundToInt | import kotlin.math.roundToInt | ||||||
| import kotlinx.coroutines.Dispatchers | import kotlinx.coroutines.Dispatchers | ||||||
|  | import kotlinx.coroutines.suspendCancellableCoroutine | ||||||
| import kotlinx.coroutines.withContext | import kotlinx.coroutines.withContext | ||||||
|  | import android.graphics.Bitmap | ||||||
|  | import android.graphics.Matrix | ||||||
|  |  | ||||||
|  | fun rotateBitmap90CounterClockwise(source: Bitmap): Bitmap { | ||||||
|  |     val width = source.width | ||||||
|  |     val height = source.height | ||||||
|  |  | ||||||
|  |     // Create a new Bitmap with swapped width and height | ||||||
|  |     val rotatedBitmap = Bitmap.createBitmap(height, width, source.config ?: Bitmap.Config.ARGB_8888) | ||||||
|  |  | ||||||
|  |     for (y in 0 until height) { | ||||||
|  |         for (x in 0 until width) { | ||||||
|  |             // Set the pixel in the new position | ||||||
|  |             rotatedBitmap.setPixel(y, width - 1 - x, source.getPixel(x, y)) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return rotatedBitmap | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | fun Bitmap.transformBitmap(orientation: Orientation): Bitmap { | ||||||
|  |     return when (orientation) { | ||||||
|  |         Orientation.PORTRAIT -> this // No transformation needed | ||||||
|  |         Orientation.LANDSCAPE_LEFT -> { | ||||||
|  |             // Transpose (swap width and height) | ||||||
|  |             val transposedBitmap = Bitmap.createBitmap(height, width, config ?: Bitmap.Config.ARGB_8888) | ||||||
|  |             for (y in 0 until height) { | ||||||
|  |                 for (x in 0 until width) { | ||||||
|  |                     transposedBitmap.setPixel(y, width - 1 - x, getPixel(x, y)) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             transposedBitmap | ||||||
|  |         } | ||||||
|  |         Orientation.PORTRAIT_UPSIDE_DOWN -> { | ||||||
|  |             // Invert vertically and horizontally (180-degree rotation) | ||||||
|  |             val invertedBitmap = Bitmap.createBitmap(width, height, config ?: Bitmap.Config.ARGB_8888) | ||||||
|  |             for (y in 0 until height) { | ||||||
|  |                 for (x in 0 until width) { | ||||||
|  |                     invertedBitmap.setPixel(width - 1 - x, height - 1 - y, getPixel(x, y)) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             invertedBitmap | ||||||
|  |         } | ||||||
|  |         Orientation.LANDSCAPE_RIGHT -> { | ||||||
|  |             // Transpose (swap width and height) and invert vertically | ||||||
|  |             val transposedBitmap = Bitmap.createBitmap(height, width, config ?: Bitmap.Config.ARGB_8888) | ||||||
|  |             for (y in 0 until height) { | ||||||
|  |                 for (x in 0 until width) { | ||||||
|  |                     transposedBitmap.setPixel(height - 1 - y, x, getPixel(x, y)) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             transposedBitmap | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @SuppressLint("ViewConstructor") | @SuppressLint("ViewConstructor") | ||||||
| class PreviewView(context: Context, callback: SurfaceHolder.Callback) : | class PreviewView(context: Context, callback: SurfaceHolder.Callback) : | ||||||
| @@ -80,6 +144,34 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) : | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   suspend fun getBitmap(): Bitmap? = withContext(Dispatchers.Main) { | ||||||
|  |     val frame = holder.getSurfaceFrame() | ||||||
|  |  | ||||||
|  |     val width = frame.width() | ||||||
|  |     val height = frame.height() | ||||||
|  |  | ||||||
|  |     val bitmap = Bitmap.createBitmap(height, width, Bitmap.Config.ARGB_8888) | ||||||
|  |  | ||||||
|  |     // Use a coroutine to suspend until the PixelCopy request is complete | ||||||
|  |     suspendCancellableCoroutine<Bitmap?> { continuation -> | ||||||
|  |     PixelCopy.request( | ||||||
|  |         holder.surface, | ||||||
|  |         bitmap, | ||||||
|  |         { copyResult -> | ||||||
|  |           if (copyResult == PixelCopy.SUCCESS) { | ||||||
|  |             continuation.resume(rotateBitmap90CounterClockwise(bitmap)) | ||||||
|  |           } else { | ||||||
|  |             continuation.resumeWithException( | ||||||
|  |                 RuntimeException("PixelCopy failed with error code $copyResult") | ||||||
|  |             ) | ||||||
|  |           } | ||||||
|  |         }, | ||||||
|  |         Handler(Looper.getMainLooper()) | ||||||
|  |     ) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|   fun convertLayerPointToCameraCoordinates(point: Point, cameraDeviceDetails: CameraDeviceDetails): Point { |   fun convertLayerPointToCameraCoordinates(point: Point, cameraDeviceDetails: CameraDeviceDetails): Point { | ||||||
|     val sensorOrientation = cameraDeviceDetails.sensorOrientation |     val sensorOrientation = cameraDeviceDetails.sensorOrientation | ||||||
|     val cameraSize = Size(cameraDeviceDetails.activeSize.width(), cameraDeviceDetails.activeSize.height()) |     val cameraSize = Size(cameraDeviceDetails.activeSize.width(), cameraDeviceDetails.activeSize.height()) | ||||||
| @@ -97,22 +189,14 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) : | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   override fun requestLayout() { |  | ||||||
|     super.requestLayout() |  | ||||||
|     // Manually trigger measure & layout, as RN on Android skips those. |  | ||||||
|     // See this issue: https://github.com/facebook/react-native/issues/17968#issuecomment-721958427 |  | ||||||
|     post { |  | ||||||
|       measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)) |  | ||||||
|       layout(left, top, right, bottom) |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private fun getSize(contentSize: Size, containerSize: Size, resizeMode: ResizeMode): Size { |   private fun getSize(contentSize: Size, containerSize: Size, resizeMode: ResizeMode): Size { | ||||||
|     var contentSize = contentSize |     var contentSize = contentSize | ||||||
|     // Swap dimensions if orientation is landscape |     var androidOrientation = context.getResources().getConfiguration().orientation; | ||||||
|     if (orientation.isLandscape()) { |  | ||||||
|  |     if (androidOrientation == Configuration.ORIENTATION_LANDSCAPE) { | ||||||
|       contentSize = Size(contentSize.height, contentSize.width) |       contentSize = Size(contentSize.height, contentSize.width) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     val contentAspectRatio = contentSize.width.toDouble() / contentSize.height |     val contentAspectRatio = contentSize.width.toDouble() / contentSize.height | ||||||
|     val containerAspectRatio = containerSize.width.toDouble() / containerSize.height |     val containerAspectRatio = containerSize.width.toDouble() / containerSize.height | ||||||
|     if (!(contentAspectRatio > 0 && containerAspectRatio > 0)) { |     if (!(contentAspectRatio > 0 && containerAspectRatio > 0)) { | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
|  | @file:Suppress("DEPRECATION") | ||||||
|  |  | ||||||
| package com.mrousavy.camera.frameprocessor | package com.mrousavy.camera.frameprocessor | ||||||
|  |  | ||||||
| import android.util.Log | import android.util.Log | ||||||
| @@ -7,14 +9,12 @@ import com.facebook.jni.HybridData | |||||||
| import com.facebook.proguard.annotations.DoNotStrip | import com.facebook.proguard.annotations.DoNotStrip | ||||||
| import com.facebook.react.bridge.ReactApplicationContext | import com.facebook.react.bridge.ReactApplicationContext | ||||||
| import com.facebook.react.bridge.UiThreadUtil | import com.facebook.react.bridge.UiThreadUtil | ||||||
| import com.facebook.react.common.annotations.FrameworkAPI | // import com.facebook.react.turbomodule.core.CallInvokerHolderImpl // Commented out due to RN 0.79+ compatibility | ||||||
| import com.facebook.react.turbomodule.core.CallInvokerHolderImpl |  | ||||||
| import com.facebook.react.uimanager.UIManagerHelper | import com.facebook.react.uimanager.UIManagerHelper | ||||||
| import com.mrousavy.camera.CameraView | import com.mrousavy.camera.CameraView | ||||||
| import com.mrousavy.camera.core.ViewNotFoundError | import com.mrousavy.camera.core.ViewNotFoundError | ||||||
| import java.lang.ref.WeakReference | import java.lang.ref.WeakReference | ||||||
|  |  | ||||||
| @OptIn(FrameworkAPI::class) |  | ||||||
| @Suppress("KotlinJniMissingFunction") // we use fbjni. | @Suppress("KotlinJniMissingFunction") // we use fbjni. | ||||||
| class VisionCameraProxy(private val reactContext: ReactApplicationContext) { | class VisionCameraProxy(private val reactContext: ReactApplicationContext) { | ||||||
|   companion object { |   companion object { | ||||||
| @@ -23,19 +23,26 @@ class VisionCameraProxy(private val reactContext: ReactApplicationContext) { | |||||||
|  |  | ||||||
|   @DoNotStrip |   @DoNotStrip | ||||||
|   @Keep |   @Keep | ||||||
|   private var mHybridData: HybridData |   private var mHybridData: HybridData? | ||||||
|   private var mContext: WeakReference<ReactApplicationContext> |   private var mContext: WeakReference<ReactApplicationContext> | ||||||
|   private var mScheduler: VisionCameraScheduler |   private var mScheduler: VisionCameraScheduler | ||||||
|   val context: ReactApplicationContext |   val context: ReactApplicationContext | ||||||
|     get() = reactContext |     get() = reactContext | ||||||
|  |  | ||||||
|   init { |   init { | ||||||
|     val jsCallInvokerHolder = context.catalystInstance.jsCallInvokerHolder as CallInvokerHolderImpl |     // TODO: Fix for React Native 0.79+ - these APIs are now framework-only | ||||||
|     val jsRuntimeHolder = |     // Since Frame Processors are disabled anyway (react-native-worklets-core not found), | ||||||
|       context.javaScriptContextHolder?.get() ?: throw Error("JSI Runtime is null! VisionCamera does not yet support bridgeless mode..") |     // we'll disable this functionality to allow the build to complete | ||||||
|  |     Log.w(TAG, "Frame Processor initialization disabled due to React Native 0.79+ API compatibility issues") | ||||||
|     mScheduler = VisionCameraScheduler() |     mScheduler = VisionCameraScheduler() | ||||||
|     mContext = WeakReference(context) |     mContext = WeakReference(context) | ||||||
|     mHybridData = initHybrid(jsRuntimeHolder, jsCallInvokerHolder, mScheduler) |     // Disable Frame Processor functionality since APIs are not compatible | ||||||
|  |     mHybridData = null | ||||||
|  |      | ||||||
|  |     // Original code that fails with RN 0.79+: | ||||||
|  |     // val jsCallInvokerHolder = context.catalystInstance.jsCallInvokerHolder as CallInvokerHolderImpl | ||||||
|  |     // val jsRuntimeHolder = context.javaScriptContextHolder?.get() ?: throw Error("JSI Runtime is null!") | ||||||
|  |     // mHybridData = initHybrid(jsRuntimeHolder, jsCallInvokerHolder, mScheduler) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @UiThread |   @UiThread | ||||||
| @@ -71,5 +78,6 @@ class VisionCameraProxy(private val reactContext: ReactApplicationContext) { | |||||||
|     FrameProcessorPluginRegistry.getPlugin(name, this, options) |     FrameProcessorPluginRegistry.getPlugin(name, this, options) | ||||||
|  |  | ||||||
|   // private C++ funcs |   // private C++ funcs | ||||||
|   private external fun initHybrid(jsContext: Long, jsCallInvokerHolder: CallInvokerHolderImpl, scheduler: VisionCameraScheduler): HybridData |   // Commented out due to React Native 0.79+ API compatibility issues | ||||||
|  |   // private external fun initHybrid(jsContext: Long, jsCallInvokerHolder: CallInvokerHolderImpl, scheduler: VisionCameraScheduler): HybridData | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,13 +1,33 @@ | |||||||
| package com.mrousavy.camera.utils | package com.mrousavy.camera.utils | ||||||
|  |  | ||||||
| import android.content.Context | import android.content.Context | ||||||
|  | import android.graphics.Bitmap | ||||||
|  | import android.graphics.BitmapFactory | ||||||
|  | import android.util.Size | ||||||
| import java.io.File | import java.io.File | ||||||
|  | import java.io.FileOutputStream | ||||||
|  |  | ||||||
| class FileUtils { | class FileUtils { | ||||||
|   companion object { |   companion object { | ||||||
|     fun createTempFile(context: Context, extension: String): File = |     fun writeBitmapTofile(bitmap: Bitmap, file: File, quality: Int) { | ||||||
|       File.createTempFile("mrousavy", extension, context.cacheDir).also { |       FileOutputStream(file).use { stream -> | ||||||
|         it.deleteOnExit() |         bitmap.compress(Bitmap.CompressFormat.JPEG, 50, stream) | ||||||
|       } |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getImageSize(imagePath: String): Size { | ||||||
|  |       val bitmapOptions = BitmapFactory.Options().also { | ||||||
|  |         it.inJustDecodeBounds = true | ||||||
|  |       } | ||||||
|  |       BitmapFactory.decodeFile(imagePath, bitmapOptions) | ||||||
|  |       val width = bitmapOptions.outWidth | ||||||
|  |       val height = bitmapOptions.outHeight | ||||||
|  |       return Size(width, height) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun createTempFile(context: Context, extension: String): File = | ||||||
|  |         File.createTempFile("mrousavy", extension, context.cacheDir).also { | ||||||
|  |       it.deleteOnExit() | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -17,8 +17,8 @@ | |||||||
|     "@react-native-community/blur": "^4.3.2", |     "@react-native-community/blur": "^4.3.2", | ||||||
|     "@react-navigation/native": "^6.1.7", |     "@react-navigation/native": "^6.1.7", | ||||||
|     "@react-navigation/native-stack": "^6.9.13", |     "@react-navigation/native-stack": "^6.9.13", | ||||||
|     "react": "^18.2.0", |     "react": "^19.1.1", | ||||||
|     "react-native": "^0.72.3", |     "react-native": "^0.81.0", | ||||||
|     "react-native-fast-image": "^8.6.3", |     "react-native-fast-image": "^8.6.3", | ||||||
|     "react-native-gesture-handler": "^2.12.1", |     "react-native-gesture-handler": "^2.12.1", | ||||||
|     "react-native-mmkv": "^2.10.2", |     "react-native-mmkv": "^2.10.2", | ||||||
| @@ -42,7 +42,7 @@ | |||||||
|     "@types/react-native-vector-icons": "^6.4.13", |     "@types/react-native-vector-icons": "^6.4.13", | ||||||
|     "@types/react-native-video": "^5.0.15", |     "@types/react-native-video": "^5.0.15", | ||||||
|     "babel-plugin-module-resolver": "^5.0.0", |     "babel-plugin-module-resolver": "^5.0.0", | ||||||
|     "eslint": "^8.46.0", |     "eslint": "^9.33.0", | ||||||
|     "eslint-plugin-prettier": "^5.0.0", |     "eslint-plugin-prettier": "^5.0.0", | ||||||
|     "metro-react-native-babel-preset": "^0.77.0", |     "metro-react-native-babel-preset": "^0.77.0", | ||||||
|     "prettier": "^3.2.4", |     "prettier": "^3.2.4", | ||||||
|   | |||||||
| @@ -47,7 +47,7 @@ export const RecordingButton: React.FC<RecordingButtonProps> = ({ style, camera, | |||||||
|           onMediaCaptured(video, 'video') |           onMediaCaptured(video, 'video') | ||||||
|           onStoppedRecording() |           onStoppedRecording() | ||||||
|         }, |         }, | ||||||
|       }) |       }, 'video.mp4') | ||||||
|       console.log('called startRecording()!') |       console.log('called startRecording()!') | ||||||
|       isRecording.current = true |       isRecording.current = true | ||||||
|       setRecordingState(true) |       setRecordingState(true) | ||||||
|   | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										189
									
								
								package/flake.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										189
									
								
								package/flake.lock
									
									
									
										generated
									
									
									
								
							| @@ -1,189 +0,0 @@ | |||||||
| { |  | ||||||
|   "nodes": { |  | ||||||
|     "android-nixpkgs": { |  | ||||||
|       "inputs": { |  | ||||||
|         "devshell": "devshell", |  | ||||||
|         "flake-utils": "flake-utils", |  | ||||||
|         "nixpkgs": "nixpkgs" |  | ||||||
|       }, |  | ||||||
|       "locked": { |  | ||||||
|         "lastModified": 1701980274, |  | ||||||
|         "narHash": "sha256-uKJIFvsahbWw52TsIht7g2iosXBgJDRMSMoCE1fvEAk=", |  | ||||||
|         "owner": "tadfisher", |  | ||||||
|         "repo": "android-nixpkgs", |  | ||||||
|         "rev": "bce9d437ed54ee1425b66442a12814fee4cdbd51", |  | ||||||
|         "type": "github" |  | ||||||
|       }, |  | ||||||
|       "original": { |  | ||||||
|         "owner": "tadfisher", |  | ||||||
|         "repo": "android-nixpkgs", |  | ||||||
|         "type": "github" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "devshell": { |  | ||||||
|       "inputs": { |  | ||||||
|         "nixpkgs": [ |  | ||||||
|           "android-nixpkgs", |  | ||||||
|           "nixpkgs" |  | ||||||
|         ], |  | ||||||
|         "systems": "systems" |  | ||||||
|       }, |  | ||||||
|       "locked": { |  | ||||||
|         "lastModified": 1701787589, |  | ||||||
|         "narHash": "sha256-ce+oQR4Zq9VOsLoh9bZT8Ip9PaMLcjjBUHVPzW5d7Cw=", |  | ||||||
|         "owner": "numtide", |  | ||||||
|         "repo": "devshell", |  | ||||||
|         "rev": "44ddedcbcfc2d52a76b64fb6122f209881bd3e1e", |  | ||||||
|         "type": "github" |  | ||||||
|       }, |  | ||||||
|       "original": { |  | ||||||
|         "owner": "numtide", |  | ||||||
|         "repo": "devshell", |  | ||||||
|         "type": "github" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "flake-utils": { |  | ||||||
|       "inputs": { |  | ||||||
|         "systems": "systems_2" |  | ||||||
|       }, |  | ||||||
|       "locked": { |  | ||||||
|         "lastModified": 1701680307, |  | ||||||
|         "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", |  | ||||||
|         "owner": "numtide", |  | ||||||
|         "repo": "flake-utils", |  | ||||||
|         "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", |  | ||||||
|         "type": "github" |  | ||||||
|       }, |  | ||||||
|       "original": { |  | ||||||
|         "owner": "numtide", |  | ||||||
|         "repo": "flake-utils", |  | ||||||
|         "type": "github" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "flake-utils_2": { |  | ||||||
|       "inputs": { |  | ||||||
|         "systems": "systems_3" |  | ||||||
|       }, |  | ||||||
|       "locked": { |  | ||||||
|         "lastModified": 1701680307, |  | ||||||
|         "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", |  | ||||||
|         "owner": "numtide", |  | ||||||
|         "repo": "flake-utils", |  | ||||||
|         "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", |  | ||||||
|         "type": "github" |  | ||||||
|       }, |  | ||||||
|       "original": { |  | ||||||
|         "owner": "numtide", |  | ||||||
|         "repo": "flake-utils", |  | ||||||
|         "type": "github" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "gitignore": { |  | ||||||
|       "inputs": { |  | ||||||
|         "nixpkgs": [ |  | ||||||
|           "nixpkgs" |  | ||||||
|         ] |  | ||||||
|       }, |  | ||||||
|       "locked": { |  | ||||||
|         "lastModified": 1694102001, |  | ||||||
|         "narHash": "sha256-vky6VPK1n1od6vXbqzOXnekrQpTL4hbPAwUhT5J9c9E=", |  | ||||||
|         "owner": "hercules-ci", |  | ||||||
|         "repo": "gitignore.nix", |  | ||||||
|         "rev": "9e21c80adf67ebcb077d75bd5e7d724d21eeafd6", |  | ||||||
|         "type": "github" |  | ||||||
|       }, |  | ||||||
|       "original": { |  | ||||||
|         "owner": "hercules-ci", |  | ||||||
|         "repo": "gitignore.nix", |  | ||||||
|         "type": "github" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "nixpkgs": { |  | ||||||
|       "locked": { |  | ||||||
|         "lastModified": 1701718080, |  | ||||||
|         "narHash": "sha256-6ovz0pG76dE0P170pmmZex1wWcQoeiomUZGggfH9XPs=", |  | ||||||
|         "owner": "NixOS", |  | ||||||
|         "repo": "nixpkgs", |  | ||||||
|         "rev": "2c7f3c0fb7c08a0814627611d9d7d45ab6d75335", |  | ||||||
|         "type": "github" |  | ||||||
|       }, |  | ||||||
|       "original": { |  | ||||||
|         "owner": "NixOS", |  | ||||||
|         "ref": "nixos-unstable", |  | ||||||
|         "repo": "nixpkgs", |  | ||||||
|         "type": "github" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "nixpkgs_2": { |  | ||||||
|       "locked": { |  | ||||||
|         "lastModified": 1702312524, |  | ||||||
|         "narHash": "sha256-gkZJRDBUCpTPBvQk25G0B7vfbpEYM5s5OZqghkjZsnE=", |  | ||||||
|         "owner": "nixos", |  | ||||||
|         "repo": "nixpkgs", |  | ||||||
|         "rev": "a9bf124c46ef298113270b1f84a164865987a91c", |  | ||||||
|         "type": "github" |  | ||||||
|       }, |  | ||||||
|       "original": { |  | ||||||
|         "owner": "nixos", |  | ||||||
|         "ref": "nixos-unstable", |  | ||||||
|         "repo": "nixpkgs", |  | ||||||
|         "type": "github" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "root": { |  | ||||||
|       "inputs": { |  | ||||||
|         "android-nixpkgs": "android-nixpkgs", |  | ||||||
|         "flake-utils": "flake-utils_2", |  | ||||||
|         "gitignore": "gitignore", |  | ||||||
|         "nixpkgs": "nixpkgs_2" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "systems": { |  | ||||||
|       "locked": { |  | ||||||
|         "lastModified": 1681028828, |  | ||||||
|         "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", |  | ||||||
|         "owner": "nix-systems", |  | ||||||
|         "repo": "default", |  | ||||||
|         "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", |  | ||||||
|         "type": "github" |  | ||||||
|       }, |  | ||||||
|       "original": { |  | ||||||
|         "owner": "nix-systems", |  | ||||||
|         "repo": "default", |  | ||||||
|         "type": "github" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "systems_2": { |  | ||||||
|       "locked": { |  | ||||||
|         "lastModified": 1681028828, |  | ||||||
|         "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", |  | ||||||
|         "owner": "nix-systems", |  | ||||||
|         "repo": "default", |  | ||||||
|         "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", |  | ||||||
|         "type": "github" |  | ||||||
|       }, |  | ||||||
|       "original": { |  | ||||||
|         "owner": "nix-systems", |  | ||||||
|         "repo": "default", |  | ||||||
|         "type": "github" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "systems_3": { |  | ||||||
|       "locked": { |  | ||||||
|         "lastModified": 1681028828, |  | ||||||
|         "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", |  | ||||||
|         "owner": "nix-systems", |  | ||||||
|         "repo": "default", |  | ||||||
|         "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", |  | ||||||
|         "type": "github" |  | ||||||
|       }, |  | ||||||
|       "original": { |  | ||||||
|         "owner": "nix-systems", |  | ||||||
|         "repo": "default", |  | ||||||
|         "type": "github" |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }, |  | ||||||
|   "root": "root", |  | ||||||
|   "version": 7 |  | ||||||
| } |  | ||||||
| @@ -1,77 +0,0 @@ | |||||||
| { |  | ||||||
|   description = "Sample Nix ts-node build"; |  | ||||||
|   inputs = { |  | ||||||
|     nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; |  | ||||||
|     flake-utils.url = "github:numtide/flake-utils"; |  | ||||||
|     gitignore = { |  | ||||||
|       url = "github:hercules-ci/gitignore.nix"; |  | ||||||
|       inputs.nixpkgs.follows = "nixpkgs"; |  | ||||||
|     }; |  | ||||||
|     android-nixpkgs = { |  | ||||||
|       url = "github:tadfisher/android-nixpkgs"; |  | ||||||
|     }; |  | ||||||
|   }; |  | ||||||
|   outputs = { |  | ||||||
|     self, |  | ||||||
|     nixpkgs, |  | ||||||
|     flake-utils, |  | ||||||
|     gitignore, |  | ||||||
|     android-nixpkgs, |  | ||||||
|     ... |  | ||||||
|   }: |  | ||||||
|     flake-utils.lib.eachDefaultSystem (system: let |  | ||||||
|       pkgs = import nixpkgs {inherit system;}; |  | ||||||
|       nodejs = pkgs.nodejs-18_x; |  | ||||||
|       # NOTE: this does not work |  | ||||||
|       appBuild = pkgs.stdenv.mkDerivation { |  | ||||||
|         name = "example-ts-node"; |  | ||||||
|         version = "0.1.0"; |  | ||||||
|         src = gitignore.lib.gitignoreSource ./.; # uses the gitignore in the repo to only copy files git would see |  | ||||||
|         buildInputs = [nodejs]; |  | ||||||
|         # https://nixos.org/manual/nixpkgs/stable/#sec-stdenv-phases |  | ||||||
|         buildPhase = '' |  | ||||||
|           # each phase has pre/postHooks. When you make your own phase be sure to still call the hooks |  | ||||||
|           runHook preBuild |  | ||||||
|           npm ci |  | ||||||
|           npm run build |  | ||||||
|           runHook postBuild |  | ||||||
|         ''; |  | ||||||
|         installPhase = '' |  | ||||||
|           runHook preInstall |  | ||||||
|           cp -r node_modules $out/node_modules |  | ||||||
|           cp package.json $out/package.json |  | ||||||
|           cp -r dist $out/dist |  | ||||||
|           runHook postInstall |  | ||||||
|         ''; |  | ||||||
|       }; |  | ||||||
|       android-sdk = android-nixpkgs.sdk.${system} (sdkPkgs: |  | ||||||
|         with sdkPkgs; [ |  | ||||||
|           cmdline-tools-latest |  | ||||||
|           build-tools-30-0-3 |  | ||||||
|           build-tools-33-0-0 |  | ||||||
|           build-tools-33-0-1 |  | ||||||
|           build-tools-34-0-0 |  | ||||||
|           platform-tools |  | ||||||
|           platforms-android-33 |  | ||||||
|           platforms-android-34 |  | ||||||
|           emulator |  | ||||||
|           ndk-23-1-7779620 |  | ||||||
|           cmake-3-22-1 |  | ||||||
|           system-images-android-33-google-apis-x86-64 |  | ||||||
|           system-images-android-34-google-apis-x86-64 |  | ||||||
|         ]); |  | ||||||
|     in |  | ||||||
|       with pkgs; { |  | ||||||
|         defaultPackage = appBuild; |  | ||||||
|         devShell = mkShell { |  | ||||||
|           buildInputs = [nodejs yarn watchman gradle_7 alejandra nodePackages.prettier ktlint kotlin-language-server]; |  | ||||||
|           ANDROID_SDK_BIN = android-sdk; |  | ||||||
|           shellHook = '' |  | ||||||
|             export JAVA_HOME=${pkgs.jdk17.home} |  | ||||||
|             source ${android-sdk.out}/nix-support/setup-hook |  | ||||||
|             export PATH=${android-sdk}/bin:$PATH |  | ||||||
|             ORG_GRADLE_PROJECT_ANDROID_HOME="$ANDROID_HOME" |  | ||||||
|           ''; |  | ||||||
|         }; |  | ||||||
|       }); |  | ||||||
| } |  | ||||||
| @@ -50,4 +50,12 @@ extension CameraView: AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureAud | |||||||
|   func resumeRecording(promise: Promise) { |   func resumeRecording(promise: Promise) { | ||||||
|     cameraSession.resumeRecording(promise: promise) |     cameraSession.resumeRecording(promise: promise) | ||||||
|   } |   } | ||||||
|  |    | ||||||
|  |   func lockExposure(promise: Promise) { | ||||||
|  |     cameraSession.lockCurrentExposure(promise: promise) | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   func unlockExposure(promise: Promise) { | ||||||
|  |     cameraSession.unlockCurrentExposure(promise: promise) | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -86,5 +86,13 @@ RCT_EXTERN_METHOD(focus | |||||||
|                   : (NSDictionary*)point resolve |                   : (NSDictionary*)point resolve | ||||||
|                   : (RCTPromiseResolveBlock)resolve reject |                   : (RCTPromiseResolveBlock)resolve reject | ||||||
|                   : (RCTPromiseRejectBlock)reject); |                   : (RCTPromiseRejectBlock)reject); | ||||||
|  | RCT_EXTERN_METHOD(lockCurrentExposure | ||||||
|  |                   : (nonnull NSNumber*)node resolve | ||||||
|  |                   : (RCTPromiseResolveBlock)resolve reject | ||||||
|  |                   : (RCTPromiseRejectBlock)reject); | ||||||
|  | RCT_EXTERN_METHOD(unlockCurrentExposure | ||||||
|  |                   : (nonnull NSNumber*)node resolve | ||||||
|  |                   : (RCTPromiseResolveBlock)resolve reject | ||||||
|  |                   : (RCTPromiseRejectBlock)reject); | ||||||
|  |  | ||||||
| @end | @end | ||||||
|   | |||||||
| @@ -111,6 +111,18 @@ final class CameraViewManager: RCTViewManager { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|    |    | ||||||
|  |   @objc | ||||||
|  |   final func lockCurrentExposure(_ node: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { | ||||||
|  |     let component = getCameraView(withTag: node) | ||||||
|  |     component.lockExposure(promise: Promise(resolver: resolve, rejecter: reject)) | ||||||
|  |   } | ||||||
|  |    | ||||||
|  |   @objc | ||||||
|  |   final func unlockCurrentExposure(_ node: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { | ||||||
|  |     let component = getCameraView(withTag: node) | ||||||
|  |     component.unlockExposure(promise: Promise(resolver: resolve, rejecter: reject)) | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // MARK: Private |   // MARK: Private | ||||||
|  |  | ||||||
|   private func getCameraView(withTag tag: NSNumber) -> CameraView { |   private func getCameraView(withTag tag: NSNumber) -> CameraView { | ||||||
|   | |||||||
| @@ -191,4 +191,68 @@ extension CameraSession { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   func lockCurrentExposure(promise: Promise) { | ||||||
|  |     CameraQueues.cameraQueue.async { | ||||||
|  |       withPromise(promise) { | ||||||
|  |         guard let captureDevice = AVCaptureDevice.default(for: .video) else { | ||||||
|  |           print("No capture device available") | ||||||
|  |           return | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         guard captureDevice.isExposureModeSupported(.custom) else { | ||||||
|  |           ReactLogger.log(level: .info, message: "Custom exposure mode not supported") | ||||||
|  |           return | ||||||
|  |         } | ||||||
|  |         do { | ||||||
|  |           // Lock the device for configuration | ||||||
|  |           try captureDevice.lockForConfiguration() | ||||||
|  |  | ||||||
|  |           // Get the current exposure duration and ISO | ||||||
|  |           let currentExposureDuration = captureDevice.exposureDuration | ||||||
|  |           let currentISO = captureDevice.iso | ||||||
|  |  | ||||||
|  |           // Check if the device supports custom exposure settings | ||||||
|  |           if captureDevice.isExposureModeSupported(.custom) { | ||||||
|  |             // Lock the current exposure and ISO by setting custom exposure mode | ||||||
|  |             captureDevice.setExposureModeCustom(duration: currentExposureDuration, iso: currentISO, completionHandler: nil) | ||||||
|  |             ReactLogger.log(level: .info, message: "Exposure and ISO locked at current values") | ||||||
|  |           } else { | ||||||
|  |             ReactLogger.log(level: .info, message:"Custom exposure mode not supported") | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           // Unlock the device after configuration | ||||||
|  |           captureDevice.unlockForConfiguration() | ||||||
|  |  | ||||||
|  |         } catch { | ||||||
|  |           ReactLogger.log(level: .warning, message:"Error locking exposure: \(error)") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return nil | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   func unlockCurrentExposure(promise: Promise) { | ||||||
|  |     CameraQueues.cameraQueue.async { | ||||||
|  |       withPromise(promise) { | ||||||
|  |         guard let captureDevice = AVCaptureDevice.default(for: .video) else { | ||||||
|  |           print("No capture device available") | ||||||
|  |           return | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         do { | ||||||
|  |           if captureDevice.isExposureModeSupported(.autoExpose) { | ||||||
|  |             try captureDevice.lockForConfiguration() | ||||||
|  |             captureDevice.exposureMode = .continuousAutoExposure | ||||||
|  |             captureDevice.unlockForConfiguration() | ||||||
|  |           } | ||||||
|  |         } catch { | ||||||
|  |           ReactLogger.log(level: .warning, message:"Error unlocking exposure: \(error)") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return nil | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -87,16 +87,15 @@ | |||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@expo/config-plugins": "^7.2.5", |     "@expo/config-plugins": "^7.2.5", | ||||||
|     "@jamesacarr/eslint-formatter-github-actions": "^0.2.0", |     "@jamesacarr/eslint-formatter-github-actions": "^0.2.0", | ||||||
|     "@react-native/eslint-config": "^0.72.2", |     "@react-native/eslint-config": "^0.81.0", | ||||||
|     "@react-native/typescript-config": "^0.74.0", |     "@react-native/typescript-config": "^0.81.0", | ||||||
|     "@release-it/conventional-changelog": "^7.0.0", |     "@release-it/conventional-changelog": "^7.0.0", | ||||||
|     "@types/react": "^18.2.19", |     "@types/react": "^18.2.19", | ||||||
|     "@types/react-native": "^0.72.2", |  | ||||||
|     "eslint": "^8.46.0", |     "eslint": "^8.46.0", | ||||||
|     "eslint-plugin-prettier": "^5.0.0", |     "eslint-plugin-prettier": "^5.0.0", | ||||||
|     "prettier": "^3.0.1", |     "prettier": "^3.0.1", | ||||||
|     "react": "^18.2.0", |     "react": "^19.1.1", | ||||||
|     "react-native": "^0.72.3", |     "react-native": "^0.81.0", | ||||||
|     "react-native-builder-bob": "^0.21.3", |     "react-native-builder-bob": "^0.21.3", | ||||||
|     "react-native-worklets-core": "^0.3.0", |     "react-native-worklets-core": "^0.3.0", | ||||||
|     "release-it": "^16.1.3", |     "release-it": "^16.1.3", | ||||||
|   | |||||||
| @@ -319,6 +319,22 @@ export class Camera extends React.PureComponent<CameraProps, CameraState> { | |||||||
|       throw tryParseNativeCameraError(e) |       throw tryParseNativeCameraError(e) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public async lockCurrentExposure(): Promise<void> { | ||||||
|  |     try { | ||||||
|  |       return await CameraModule.lockCurrentExposure(this.handle) | ||||||
|  |     } catch (e) { | ||||||
|  |       throw tryParseNativeCameraError(e) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public async unlockCurrentExposure(): Promise<void> { | ||||||
|  |     try { | ||||||
|  |       return await CameraModule.unlockCurrentExposure(this.handle) | ||||||
|  |     } catch (e) { | ||||||
|  |       throw tryParseNativeCameraError(e) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|   //#endregion |   //#endregion | ||||||
|  |  | ||||||
|   //#region Static Functions (NativeModule) |   //#region Static Functions (NativeModule) | ||||||
|   | |||||||
							
								
								
									
										3055
									
								
								package/yarn.lock
									
									
									
									
									
								
							
							
						
						
									
										3055
									
								
								package/yarn.lock
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user