Merge pull request #330 from aarongrider/android-rewrite
Rewrite Android implementation, update api and docs, convert code to TypeScript
This commit is contained in:
commit
affcdb4571
@ -50,7 +50,7 @@ module.exports = {
|
||||
'import/ignore': ['node_modules/react-native/index\\.js$'], // https://github.com/facebook/react-native/issues/28549
|
||||
'import/resolver': {
|
||||
node: {
|
||||
extensions: ['.js', '.ts', '.android.js', '.ios.js'],
|
||||
extensions: ['.js', '.ts', '.android.js', '.ios.js', '.android.tsx', '.ios.tsx'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
79
README.md
79
README.md
@ -48,19 +48,17 @@ cd ios && pod install && cd ..
|
||||
|
||||
## APIs
|
||||
|
||||
### CameraKitCamera - Camera component
|
||||
### Camera - Base Camera component
|
||||
|
||||
```js
|
||||
import { CameraKitCamera } from 'react-native-camera-kit';
|
||||
import { Camera } from 'react-native-camera-kit';
|
||||
```
|
||||
|
||||
```jsx
|
||||
<CameraKitCamera
|
||||
ref={(cam) => (this.camera = cam)}
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: 'white',
|
||||
}}
|
||||
<Camera
|
||||
ref={(ref) => this.camera = ref}
|
||||
type={CameraType.Back} // front/back(default)
|
||||
style={{ flex: 1 }}
|
||||
cameraOptions={{
|
||||
flashMode: 'auto', // on/off/auto(default)
|
||||
focusMode: 'on', // off/on(default)
|
||||
@ -68,11 +66,18 @@ import { CameraKitCamera } from 'react-native-camera-kit';
|
||||
ratioOverlay: '1:1', // optional
|
||||
ratioOverlayColor: '#00000077', // optional
|
||||
}}
|
||||
resetFocusTimeout={0} // optional
|
||||
resetFocusWhenMotionDetected={true} // optional
|
||||
saveToCameraRole={false} // optional
|
||||
// Barcode Scanner Props
|
||||
scanBarcode={false} // optional
|
||||
showFrame={false} // Barcode only, optional
|
||||
laserColor='red' // Barcode only, optional
|
||||
frameColor='yellow' // Barcode only, optional
|
||||
surfaceColor='blue' // Barcode only, optional
|
||||
onReadCode={(
|
||||
event, // optional
|
||||
) => console.log(event.nativeEvent.codeStringValue)}
|
||||
resetFocusTimeout={0} // optional
|
||||
resetFocusWhenMotionDetected={true} // optional
|
||||
/>
|
||||
```
|
||||
|
||||
@ -94,12 +99,20 @@ import { CameraKitCamera } from 'react-native-camera-kit';
|
||||
| `ratioOverlay` | `['int':'int', ...]` | Show a guiding overlay in the camera preview for the selected ratio. Does not crop image as of v9.0. Example: `['16:9', '1:1', '3:4']` |
|
||||
| `ratioOverlayColor` | Color | Any color with alpha (default is `'#ffffff77'`) |
|
||||
|
||||
### CameraKitCamera API
|
||||
### Camera API
|
||||
|
||||
#### checkDeviceCameraAuthorizationStatus
|
||||
#### capture({ ... }) - must have the wanted camera capture reference
|
||||
|
||||
Capture image (`{ saveToCameraRoll: boolean }`). Using the camera roll is slower than using regular files stored in your app. On an iPhone X in debug mode, on a real phone, we measured around 100-150ms processing time to save to the camera roll.
|
||||
|
||||
```js
|
||||
const isCameraAuthorized = await CameraKitCamera.checkDeviceCameraAuthorizationStatus();
|
||||
const image = await this.camera.capture();
|
||||
```
|
||||
|
||||
#### checkDeviceCameraAuthorizationStatus (iOS only)
|
||||
|
||||
```js
|
||||
const isCameraAuthorized = await Camera.checkDeviceCameraAuthorizationStatus();
|
||||
```
|
||||
|
||||
return values:
|
||||
@ -110,54 +123,22 @@ return values:
|
||||
|
||||
otherwise, returns `false`
|
||||
|
||||
#### requestDeviceCameraAuthorization
|
||||
#### requestDeviceCameraAuthorization (iOS only)
|
||||
|
||||
```js
|
||||
const isUserAuthorizedCamera = await CameraKitCamera.requestDeviceCameraAuthorization();
|
||||
const isUserAuthorizedCamera = await Camera.requestDeviceCameraAuthorization();
|
||||
```
|
||||
|
||||
`AVAuthorizationStatusAuthorized` returns `true`
|
||||
|
||||
otherwise, returns `false`
|
||||
|
||||
#### capture({ ... }) - must have the wanted camera capture reference
|
||||
|
||||
Capture image (`{ saveToCameraRoll: boolean }`). Using the camera roll is slower than using regular files stored in your app. On an iPhone X in debug mode, on a real phone, we measured around 100-150ms processing time to save to the camera roll.
|
||||
|
||||
```js
|
||||
const image = await this.camera.capture();
|
||||
```
|
||||
|
||||
#### setFlashMode - must have the wanted camera capture reference
|
||||
|
||||
Set flash mode (`auto`/`on`/`off`)
|
||||
|
||||
```js
|
||||
const success = await this.camera.setFlashMode(newFlashData.mode);
|
||||
```
|
||||
|
||||
#### setTorchMode - must have the wanted camera capture reference
|
||||
|
||||
Set Torch mode (`on`/`off`)
|
||||
|
||||
```js
|
||||
const success = await this.camera.setTorchMode(newTorchMode);
|
||||
```
|
||||
|
||||
#### changeCamera - must have the wanted camera capture reference
|
||||
|
||||
Change to front/rear camera
|
||||
|
||||
```js
|
||||
const success = await this.camera.changeCamera();
|
||||
```
|
||||
|
||||
## QR Code
|
||||
|
||||
```js
|
||||
import { CameraKitCameraScreen } from 'react-native-camera-kit';
|
||||
import { CameraScreen } from 'react-native-camera-kit';
|
||||
|
||||
<CameraKitCameraScreen
|
||||
<CameraScreen
|
||||
actions={{ rightButtonText: 'Done', leftButtonText: 'Cancel' }}
|
||||
onBottomButtonPressed={(event) => this.onBottomButtonPressed(event)}
|
||||
scanBarcode={true}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
@ -21,4 +22,16 @@ dependencies {
|
||||
implementation 'com.facebook.react:react-native:+'
|
||||
implementation 'com.google.zxing:core:3.3.3'
|
||||
implementation group: 'com.drewnoakes', name: 'metadata-extractor', version: '2.12.0'
|
||||
implementation "androidx.core:core-ktx:+"
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
implementation "androidx.camera:camera-core:1.0.0-beta08"
|
||||
implementation "androidx.camera:camera-camera2:1.0.0-beta08"
|
||||
implementation "androidx.camera:camera-lifecycle:1.0.0-beta08"
|
||||
implementation "androidx.camera:camera-view:1.0.0-alpha15"
|
||||
|
||||
implementation 'com.google.mlkit:barcode-scanning:16.0.3'
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
420
android/src/main/java/com/rncamerakit/CKCamera.kt
Normal file
420
android/src/main/java/com/rncamerakit/CKCamera.kt
Normal file
@ -0,0 +1,420 @@
|
||||
package com.rncamerakit
|
||||
|
||||
import android.Manifest
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Color
|
||||
import android.hardware.SensorManager
|
||||
import android.media.AudioManager
|
||||
import android.media.MediaActionSound
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.camera.core.*
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.camera.view.PreviewView
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.Promise
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.facebook.react.common.ReactConstants.TAG
|
||||
import com.facebook.react.uimanager.ThemedReactContext
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter
|
||||
import java.io.File
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@SuppressLint("ViewConstructor") // Extra constructors unused. Not using visual layout tools
|
||||
class CKCamera(context: ThemedReactContext) : FrameLayout(context), LifecycleObserver {
|
||||
private val currentContext: ThemedReactContext = context
|
||||
private var preview: Preview? = null
|
||||
private var imageCapture: ImageCapture? = null
|
||||
private var qrCodeAnalyzer: ImageAnalysis? = null
|
||||
private var cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||
private var camera: Camera? = null
|
||||
private var orientationListener: OrientationEventListener? = null
|
||||
private var viewFinder: PreviewView = PreviewView(context)
|
||||
private var cameraExecutor: ExecutorService = Executors.newSingleThreadExecutor()
|
||||
private var scanBarcode: Boolean = false
|
||||
private var lensType = CameraSelector.LENS_FACING_BACK
|
||||
private var autoFocus = "on"
|
||||
private var cameraProvider: ProcessCameraProvider? = null
|
||||
private var outputPath: String? = null
|
||||
private var shutterAnimationDuration: Int = 50
|
||||
private var effectLayer = View(context)
|
||||
private var counter = 0
|
||||
|
||||
private fun getActivity() : Activity {
|
||||
return currentContext.currentActivity!!
|
||||
}
|
||||
|
||||
init {
|
||||
viewFinder.layoutParams = LinearLayout.LayoutParams(
|
||||
LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.MATCH_PARENT
|
||||
)
|
||||
installHierarchyFitter(viewFinder)
|
||||
addView(viewFinder)
|
||||
|
||||
effectLayer.alpha = 0F
|
||||
effectLayer.setBackgroundColor(Color.BLACK)
|
||||
addView(effectLayer)
|
||||
|
||||
if (hasPermissions()) {
|
||||
viewFinder.post { startCamera() }
|
||||
}
|
||||
|
||||
(getActivity() as AppCompatActivity).lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
fun onResume() {
|
||||
Log.d(TAG, "onResume")
|
||||
viewFinder.post { startCamera() }
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
fun onPause() {
|
||||
Log.d(TAG, "onPause")
|
||||
stopCamera()
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
|
||||
fun onDestroy() {
|
||||
Log.d(TAG, "onDestroy")
|
||||
stopCamera()
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
stopCamera()
|
||||
}
|
||||
|
||||
private fun stopCamera() {
|
||||
cameraExecutor.shutdown()
|
||||
orientationListener?.disable()
|
||||
cameraProvider?.unbindAll()
|
||||
}
|
||||
|
||||
// If this is not called correctly, view finder will be black/blank
|
||||
// https://github.com/facebook/react-native/issues/17968#issuecomment-633308615
|
||||
private fun installHierarchyFitter(view: ViewGroup) {
|
||||
Log.d(TAG, "CameraView looking for ThemedReactContext")
|
||||
if (context is ThemedReactContext) { // only react-native setup
|
||||
Log.d(TAG, "CameraView found ThemedReactContext")
|
||||
view.setOnHierarchyChangeListener(object : OnHierarchyChangeListener {
|
||||
override fun onChildViewRemoved(parent: View?, child: View?) = Unit
|
||||
override fun onChildViewAdded(parent: View?, child: View?) {
|
||||
parent?.measure(
|
||||
MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)
|
||||
)
|
||||
parent?.layout(0, 0, parent.measuredWidth, parent.measuredHeight)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun startCamera() {
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(getActivity())
|
||||
|
||||
val onScaleGestureListener = object: ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||
override fun onScale(detector: ScaleGestureDetector?): Boolean {
|
||||
val cameraControl = camera?.cameraControl ?: return true
|
||||
val zoom = camera?.cameraInfo?.zoomState?.value?.zoomRatio ?: return true
|
||||
val scaleFactor = detector?.scaleFactor ?: return true
|
||||
val scale = zoom * scaleFactor
|
||||
|
||||
cameraControl.setZoomRatio(scale)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
cameraProviderFuture.addListener(Runnable {
|
||||
// Used to bind the lifecycle of cameras to the lifecycle owner
|
||||
cameraProvider = cameraProviderFuture.get()
|
||||
val scaleDetector = ScaleGestureDetector(context, onScaleGestureListener)
|
||||
|
||||
cameraSelector = CameraSelector.Builder().requireLensFacing(lensType).build()
|
||||
preview = Preview.Builder().build().also {
|
||||
it.setSurfaceProvider(viewFinder.createSurfaceProvider())
|
||||
}
|
||||
imageCapture = ImageCapture.Builder().build()
|
||||
|
||||
// Rotate the image according to device orientation, even when UI orientation is locked
|
||||
orientationListener = object : OrientationEventListener(context, SensorManager.SENSOR_DELAY_UI) {
|
||||
override fun onOrientationChanged(orientation: Int) {
|
||||
val imageCapture = imageCapture ?: return
|
||||
var newOrientation: Int = imageCapture.targetRotation
|
||||
if (orientation >= 315 || orientation < 45) {
|
||||
newOrientation = Surface.ROTATION_0
|
||||
} else if (orientation in 225..314) {
|
||||
newOrientation = Surface.ROTATION_90
|
||||
} else if (orientation in 135..224) {
|
||||
newOrientation = Surface.ROTATION_180
|
||||
} else if (orientation in 45..134) {
|
||||
newOrientation = Surface.ROTATION_270
|
||||
}
|
||||
if (newOrientation != imageCapture.targetRotation) {
|
||||
imageCapture.targetRotation = newOrientation
|
||||
onOrientationChange(newOrientation)
|
||||
}
|
||||
}
|
||||
}
|
||||
orientationListener!!.enable()
|
||||
|
||||
// Contain camera feed image within component bounds, centered
|
||||
viewFinder.scaleType = PreviewView.ScaleType.FIT_CENTER
|
||||
|
||||
// Tap to focus
|
||||
viewFinder.setOnTouchListener { _, event ->
|
||||
if (event.action != MotionEvent.ACTION_UP) {
|
||||
return@setOnTouchListener scaleDetector.onTouchEvent(event)
|
||||
}
|
||||
focusOnPoint(event.x, event.y)
|
||||
return@setOnTouchListener true
|
||||
}
|
||||
|
||||
qrCodeAnalyzer = ImageAnalysis.Builder()
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.build()
|
||||
|
||||
val analyzer = QRCodeAnalyzer { barcodes ->
|
||||
if (barcodes.isNotEmpty()) {
|
||||
onBarcodeRead(barcodes)
|
||||
}
|
||||
}
|
||||
qrCodeAnalyzer!!.setAnalyzer(cameraExecutor, analyzer)
|
||||
|
||||
val useCases = mutableListOf(preview, imageCapture)
|
||||
if (scanBarcode) useCases.add(qrCodeAnalyzer!!)
|
||||
|
||||
try {
|
||||
// Unbind use cases before rebinding
|
||||
cameraProvider!!.unbindAll()
|
||||
|
||||
// Bind use cases to camera
|
||||
camera = cameraProvider!!.bindToLifecycle(
|
||||
getActivity() as AppCompatActivity,
|
||||
cameraSelector,
|
||||
*useCases.toTypedArray()
|
||||
)
|
||||
preview!!.setSurfaceProvider(viewFinder.createSurfaceProvider())
|
||||
Log.d(TAG, "CameraView: Use cases bound")
|
||||
} catch (exc: Exception) {
|
||||
Log.e(TAG, "CameraView: Use cases binding failed", exc)
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(getActivity()))
|
||||
}
|
||||
|
||||
private fun flashViewFinder() {
|
||||
if (shutterAnimationDuration == 0) return
|
||||
|
||||
effectLayer
|
||||
.animate()
|
||||
.alpha(1F)
|
||||
.setDuration(shutterAnimationDuration.toLong())
|
||||
.setListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
effectLayer.animate().alpha(0F).duration = shutterAnimationDuration.toLong()
|
||||
}
|
||||
}).start()
|
||||
}
|
||||
|
||||
fun setShutterAnimationDuration(duration: Int) {
|
||||
shutterAnimationDuration = duration
|
||||
}
|
||||
|
||||
fun capture(options: Map<String, Any>, promise: Promise) {
|
||||
// Create output options object which contains file + metadata
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, "Untitled")
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
|
||||
}
|
||||
|
||||
// Create the output file option to store the captured image in MediaStore
|
||||
val outputOptions = when (outputPath) {
|
||||
null -> ImageCapture.OutputFileOptions
|
||||
.Builder(
|
||||
context.contentResolver,
|
||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
|
||||
contentValues
|
||||
)
|
||||
.build()
|
||||
else -> ImageCapture.OutputFileOptions
|
||||
.Builder(File(outputPath))
|
||||
.build()
|
||||
}
|
||||
|
||||
flashViewFinder()
|
||||
|
||||
val audio = getActivity().getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
if (audio.ringerMode == AudioManager.RINGER_MODE_NORMAL) {
|
||||
MediaActionSound().play(MediaActionSound.SHUTTER_CLICK);
|
||||
}
|
||||
|
||||
// Setup image capture listener which is triggered after photo has
|
||||
// been taken
|
||||
imageCapture?.takePicture(
|
||||
outputOptions, ContextCompat.getMainExecutor(getActivity()), object : ImageCapture.OnImageSavedCallback {
|
||||
override fun onError(ex: ImageCaptureException) {
|
||||
Log.e(TAG, "CameraView: Photo capture failed: ${ex.message}", ex)
|
||||
promise.reject("E_CAPTURE_FAILED", "takePicture failed: ${ex.message}")
|
||||
}
|
||||
|
||||
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
|
||||
try {
|
||||
val savedUri = output.savedUri.toString()
|
||||
onPictureTaken(savedUri)
|
||||
Log.d(TAG, "CameraView: Photo capture succeeded: $savedUri")
|
||||
|
||||
val imageInfo = Arguments.createMap()
|
||||
imageInfo.putString("uri", savedUri)
|
||||
imageInfo.putString("id", output.savedUri?.path)
|
||||
imageInfo.putString("name", output.savedUri?.lastPathSegment)
|
||||
// imageInfo.putInt("size", null)
|
||||
imageInfo.putInt("width", width)
|
||||
imageInfo.putInt("height", height)
|
||||
imageInfo.putString("path", output.savedUri?.path)
|
||||
|
||||
promise.resolve(imageInfo)
|
||||
} catch (ex: Exception) {
|
||||
Log.e(TAG, "Error while saving or decoding saved photo: ${ex.message}", ex)
|
||||
promise.reject("E_ON_IMG_SAVED", "Error while reading saved photo: ${ex.message}")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun focusOnPoint(x: Float?, y: Float?) {
|
||||
if (x === null || y === null) {
|
||||
camera?.cameraControl?.cancelFocusAndMetering()
|
||||
return
|
||||
}
|
||||
val factory = viewFinder.meteringPointFactory
|
||||
val builder = FocusMeteringAction.Builder(factory.createPoint(x, y))
|
||||
|
||||
// Auto-cancel will clear focus points (and engage AF) after a duration
|
||||
if (autoFocus == "off") builder.disableAutoCancel()
|
||||
|
||||
camera?.cameraControl?.startFocusAndMetering(builder.build())
|
||||
}
|
||||
|
||||
private fun onBarcodeRead(barcodes: List<String>) {
|
||||
val event: WritableMap = Arguments.createMap()
|
||||
event.putArray("barcodes", Arguments.makeNativeArray(barcodes))
|
||||
currentContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(
|
||||
id,
|
||||
"onBarcodeRead",
|
||||
event
|
||||
)
|
||||
}
|
||||
|
||||
private fun onOrientationChange(orientation: Int) {
|
||||
val remappedOrientation = when (orientation) {
|
||||
Surface.ROTATION_0 -> RNCameraKitModule.PORTRAIT
|
||||
Surface.ROTATION_90 -> RNCameraKitModule.LANDSCAPE_LEFT
|
||||
Surface.ROTATION_180 -> RNCameraKitModule.PORTRAIT_UPSIDE_DOWN
|
||||
Surface.ROTATION_270 -> RNCameraKitModule.LANDSCAPE_RIGHT
|
||||
else -> {
|
||||
Log.e(TAG, "CameraView: Unknown device orientation detected: $orientation")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val event: WritableMap = Arguments.createMap()
|
||||
event.putInt("orientation", remappedOrientation)
|
||||
currentContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(
|
||||
id,
|
||||
"onOrientationChange",
|
||||
event
|
||||
)
|
||||
}
|
||||
|
||||
private fun onPictureTaken(uri: String) {
|
||||
val event: WritableMap = Arguments.createMap()
|
||||
event.putString("uri", uri)
|
||||
currentContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(
|
||||
id,
|
||||
"onPictureTaken",
|
||||
event
|
||||
)
|
||||
}
|
||||
|
||||
fun setFlashMode(mode: String?) {
|
||||
val imageCapture = imageCapture ?: return
|
||||
val camera = camera ?: return
|
||||
when (mode) {
|
||||
"torch" -> camera.cameraControl.enableTorch(true)
|
||||
"on" -> {
|
||||
camera.cameraControl.enableTorch(false)
|
||||
imageCapture.flashMode = ImageCapture.FLASH_MODE_ON
|
||||
}
|
||||
"off" -> {
|
||||
camera.cameraControl.enableTorch(false)
|
||||
imageCapture.flashMode = ImageCapture.FLASH_MODE_OFF
|
||||
}
|
||||
else -> { // 'auto' and any wrong values
|
||||
imageCapture.flashMode = ImageCapture.FLASH_MODE_AUTO
|
||||
camera.cameraControl.enableTorch(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setAutoFocus(mode: String = "on") {
|
||||
autoFocus = mode
|
||||
when(mode) {
|
||||
// "cancel" clear AF points and engages continuous auto-focus
|
||||
"on" -> camera?.cameraControl?.cancelFocusAndMetering()
|
||||
// 'off': Handled when you tap to focus
|
||||
}
|
||||
}
|
||||
|
||||
fun setScanBarcode(enabled: Boolean) {
|
||||
val restartCamera = enabled != scanBarcode
|
||||
scanBarcode = enabled
|
||||
if (restartCamera) startCamera()
|
||||
}
|
||||
|
||||
fun setType(type: String = "back") {
|
||||
val newLensType = when (type) {
|
||||
"front" -> CameraSelector.LENS_FACING_FRONT
|
||||
else -> CameraSelector.LENS_FACING_BACK
|
||||
}
|
||||
val restartCamera = lensType != newLensType
|
||||
lensType = newLensType
|
||||
if (restartCamera) startCamera()
|
||||
}
|
||||
|
||||
fun setOutputPath(path: String) {
|
||||
outputPath = path
|
||||
}
|
||||
|
||||
private fun hasPermissions(): Boolean {
|
||||
val requiredPermissions = arrayOf(Manifest.permission.CAMERA)
|
||||
if (requiredPermissions.all {
|
||||
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
|
||||
}) {
|
||||
return true
|
||||
}
|
||||
ActivityCompat.requestPermissions(
|
||||
getActivity(),
|
||||
requiredPermissions,
|
||||
42 // random callback identifier
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
79
android/src/main/java/com/rncamerakit/CKCameraManager.kt
Normal file
79
android/src/main/java/com/rncamerakit/CKCameraManager.kt
Normal file
@ -0,0 +1,79 @@
|
||||
package com.rncamerakit
|
||||
|
||||
import android.util.Log
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableType
|
||||
import com.facebook.react.common.MapBuilder
|
||||
import com.facebook.react.common.ReactConstants.TAG
|
||||
import com.facebook.react.uimanager.SimpleViewManager
|
||||
import com.facebook.react.uimanager.ThemedReactContext
|
||||
import com.facebook.react.uimanager.annotations.ReactProp
|
||||
|
||||
class CKCameraManager : SimpleViewManager<CKCamera>() {
|
||||
|
||||
override fun getName() : String {
|
||||
return "CKCameraManager"
|
||||
}
|
||||
|
||||
override fun createViewInstance(context: ThemedReactContext): CKCamera {
|
||||
return CKCamera(context)
|
||||
}
|
||||
|
||||
override fun receiveCommand(view: CKCamera, commandId: String?, args: ReadableArray?) {
|
||||
var logCommand = "CameraManager received command $commandId("
|
||||
for (i in 0..(args?.size() ?: 0)) {
|
||||
if (i > 0) {
|
||||
logCommand += ", "
|
||||
}
|
||||
logCommand += when (args?.getType(0)) {
|
||||
ReadableType.Null -> "Null"
|
||||
ReadableType.Array -> "Array"
|
||||
ReadableType.Boolean -> "Boolean"
|
||||
ReadableType.Map -> "Map"
|
||||
ReadableType.Number -> "Number"
|
||||
ReadableType.String -> "String"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
logCommand += ")"
|
||||
Log.d(TAG, logCommand)
|
||||
}
|
||||
|
||||
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> {
|
||||
return MapBuilder.of(
|
||||
"onOrientationChange", MapBuilder.of("registrationName", "onOrientationChange"),
|
||||
"onBarcodeRead", MapBuilder.of("registrationName", "onBarcodeRead"),
|
||||
"onPictureTaken", MapBuilder.of("registrationName", "onPictureTaken")
|
||||
)
|
||||
}
|
||||
|
||||
@ReactProp(name = "flashMode")
|
||||
fun setFlashMode(view: CKCamera, mode: String?) {
|
||||
view.setFlashMode(mode)
|
||||
}
|
||||
|
||||
@ReactProp(name = "scanBarcode")
|
||||
fun setScanBarcode(view: CKCamera, enabled: Boolean) {
|
||||
view.setScanBarcode(enabled)
|
||||
}
|
||||
|
||||
@ReactProp(name = "type")
|
||||
fun setType(view: CKCamera, type: String) {
|
||||
view.setType(type)
|
||||
}
|
||||
|
||||
@ReactProp(name = "autoFocus")
|
||||
fun setAutoFocus(view: CKCamera, mode: String) {
|
||||
view.setAutoFocus(mode)
|
||||
}
|
||||
|
||||
@ReactProp(name = "outputPath")
|
||||
fun setOutputPath(view: CKCamera, path: String) {
|
||||
view.setOutputPath(path)
|
||||
}
|
||||
|
||||
@ReactProp(name = "shutterAnimationDuration")
|
||||
fun setShutterAnimationDuration(view: CKCamera, duration: Int) {
|
||||
view.setShutterAnimationDuration(duration)
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
package com.rncamerakit;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
public class DeviceUtils {
|
||||
public static boolean isGoogleDevice() {
|
||||
return (Build.MANUFACTURER.toLowerCase().contains("google") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) ||
|
||||
(Build.MANUFACTURER.toLowerCase().contains("lge") && Build.BRAND.toLowerCase().equals("google"));
|
||||
}
|
||||
}
|
||||
29
android/src/main/java/com/rncamerakit/QRCodeAnalyzer.kt
Normal file
29
android/src/main/java/com/rncamerakit/QRCodeAnalyzer.kt
Normal file
@ -0,0 +1,29 @@
|
||||
package com.rncamerakit
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.ImageProxy
|
||||
import com.google.mlkit.vision.barcode.BarcodeScanning
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
|
||||
class QRCodeAnalyzer (
|
||||
private val onQRCodesDetected: (qrCodes: List<String>) -> Unit
|
||||
) : ImageAnalysis.Analyzer {
|
||||
@SuppressLint("UnsafeExperimentalUsageError")
|
||||
override fun analyze(image: ImageProxy) {
|
||||
val inputImage = InputImage.fromMediaImage(image.image!!, image.imageInfo.rotationDegrees)
|
||||
|
||||
val scanner = BarcodeScanning.getClient()
|
||||
scanner.process(inputImage)
|
||||
.addOnSuccessListener { barcodes ->
|
||||
val strBarcodes = mutableListOf<String>()
|
||||
barcodes.forEach { barcode ->
|
||||
strBarcodes.add(barcode.rawValue ?: return@forEach)
|
||||
}
|
||||
onQRCodesDetected(strBarcodes)
|
||||
}
|
||||
.addOnCompleteListener{
|
||||
image.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
40
android/src/main/java/com/rncamerakit/RNCameraKitModule.kt
Normal file
40
android/src/main/java/com/rncamerakit/RNCameraKitModule.kt
Normal file
@ -0,0 +1,40 @@
|
||||
package com.rncamerakit
|
||||
|
||||
import com.facebook.react.bridge.*
|
||||
import com.facebook.react.uimanager.UIManagerModule
|
||||
|
||||
class RNCameraKitModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
|
||||
|
||||
companion object {
|
||||
// 0-indexed, rotates counter-clockwise
|
||||
// Values map to CameraX's Surface.ROTATION_* constants
|
||||
const val PORTRAIT = 0 // ⬆️
|
||||
const val LANDSCAPE_LEFT = 1 // ⬅️
|
||||
const val PORTRAIT_UPSIDE_DOWN = 2 // ⬇️
|
||||
const val LANDSCAPE_RIGHT = 3 // ➡️
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return "RNCameraKitModule"
|
||||
}
|
||||
|
||||
override fun getConstants(): Map<String, Any> {
|
||||
return hashMapOf(
|
||||
"PORTRAIT" to PORTRAIT,
|
||||
"PORTRAIT_UPSIDE_DOWN" to PORTRAIT_UPSIDE_DOWN,
|
||||
"LANDSCAPE_LEFT" to LANDSCAPE_LEFT,
|
||||
"LANDSCAPE_RIGHT" to LANDSCAPE_RIGHT
|
||||
)
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun capture(options: ReadableMap, viewTag: Int, promise: Promise) {
|
||||
// CameraManager does not allow us to return values
|
||||
val context = reactContext
|
||||
val uiManager = context.getNativeModule(UIManagerModule::class.java)
|
||||
context.runOnUiQueueThread {
|
||||
val view = uiManager.resolveView(viewTag) as CKCamera
|
||||
view.capture(options.toHashMap(), promise)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
package com.rncamerakit;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
import com.facebook.react.bridge.NativeModule;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.rncamerakit.camera.CameraModule;
|
||||
import com.rncamerakit.camera.CameraViewManager;
|
||||
import com.rncamerakit.camera.permission.CameraPermissionRequestCallback;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class RNCameraKitPackage implements ReactPackage {
|
||||
|
||||
@Nullable private CameraPermissionRequestCallback cameraPermissionRequestCallback;
|
||||
|
||||
public RNCameraKitPackage() {
|
||||
|
||||
}
|
||||
|
||||
public RNCameraKitPackage(CameraPermissionRequestCallback cameraPermissionRequestCallback) {
|
||||
this.cameraPermissionRequestCallback = cameraPermissionRequestCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
|
||||
List<NativeModule> modules = new ArrayList<>();
|
||||
|
||||
CameraModule cameraModule = new CameraModule(reactContext);
|
||||
if (cameraPermissionRequestCallback != null) {
|
||||
cameraPermissionRequestCallback.setCameraModule(cameraModule);
|
||||
}
|
||||
modules.add(cameraModule);
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
// Deprecated RN 0.47
|
||||
public List<Class<? extends JavaScriptModule>> createJSModules() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
List<ViewManager> viewManagers = new ArrayList<>();
|
||||
viewManagers.add(new CameraViewManager());
|
||||
return viewManagers;
|
||||
}
|
||||
|
||||
}
|
||||
22
android/src/main/java/com/rncamerakit/RNCameraKitPackage.kt
Normal file
22
android/src/main/java/com/rncamerakit/RNCameraKitPackage.kt
Normal file
@ -0,0 +1,22 @@
|
||||
package com.rncamerakit
|
||||
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.bridge.NativeModule
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.uimanager.ViewManager
|
||||
import java.util.*
|
||||
|
||||
class RNCameraKitPackage : ReactPackage {
|
||||
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
||||
val modules: MutableList<NativeModule> = ArrayList()
|
||||
val cameraModule = RNCameraKitModule(reactContext)
|
||||
modules.add(cameraModule)
|
||||
return modules
|
||||
}
|
||||
|
||||
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
||||
val viewManagers: MutableList<ViewManager<*, *>> = ArrayList()
|
||||
viewManagers.add(CKCameraManager())
|
||||
return viewManagers
|
||||
}
|
||||
}
|
||||
@ -1,261 +0,0 @@
|
||||
package com.rncamerakit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Matrix;
|
||||
import android.hardware.Camera;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
import android.util.Patterns;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.drew.imaging.ImageMetadataReader;
|
||||
import com.drew.imaging.ImageProcessingException;
|
||||
import com.drew.metadata.Metadata;
|
||||
import com.drew.metadata.MetadataException;
|
||||
import com.drew.metadata.exif.ExifIFD0Directory;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.rncamerakit.camera.CameraViewManager;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
|
||||
import static com.facebook.react.common.ReactConstants.TAG;
|
||||
|
||||
public class SaveImageTask extends AsyncTask<byte[], Void, Void> {
|
||||
|
||||
private final Context context;
|
||||
private final Promise promise;
|
||||
private boolean saveToCameraRoll;
|
||||
private String bitmapUrl = null;
|
||||
|
||||
public SaveImageTask(Context context, Promise promise, boolean saveToCameraRoll) {
|
||||
this.context = context;
|
||||
this.promise = promise;
|
||||
this.saveToCameraRoll = saveToCameraRoll;
|
||||
}
|
||||
|
||||
public SaveImageTask(String bitmapUrl, Context context, Promise promise, boolean saveToCameraRoll) {
|
||||
this(context, promise, saveToCameraRoll);
|
||||
this.bitmapUrl = bitmapUrl;
|
||||
if (this.bitmapUrl != null) {
|
||||
this.bitmapUrl = this.bitmapUrl.replace("file://","");
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap getImageBitmapFromRemoteImageFile() {
|
||||
Bitmap image;
|
||||
try {
|
||||
URL url = new URL(bitmapUrl);
|
||||
image = BitmapFactory.decodeStream(url.openStream());
|
||||
} catch (IOException e) {
|
||||
image = null;
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
private Bitmap getImageBitmapFromLocalImageFile() {
|
||||
Bitmap image;
|
||||
FileInputStream fis;
|
||||
File imageFile = new File(bitmapUrl);
|
||||
try {
|
||||
fis = new FileInputStream(imageFile);
|
||||
image = BitmapFactory.decodeStream(fis);
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
image = null;
|
||||
}
|
||||
|
||||
if (imageFile.exists()) {
|
||||
imageFile.delete();
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
private Bitmap getImageBitmap(byte[]... data) {
|
||||
Bitmap image;
|
||||
if (bitmapUrl != null) {
|
||||
if (Patterns.WEB_URL.matcher(bitmapUrl.toLowerCase()).matches()) {
|
||||
image = getImageBitmapFromRemoteImageFile();
|
||||
} else {
|
||||
image = getImageBitmapFromLocalImageFile();
|
||||
}
|
||||
}
|
||||
else {
|
||||
byte[] rawImageData = data[0];
|
||||
image = decodeAndRotateIfNeeded(rawImageData);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(byte[]... data) {
|
||||
Bitmap image = getImageBitmap(data);
|
||||
if (image == null) {
|
||||
promise.reject("CameraKit", "failed to get Bitmap image");
|
||||
return null;
|
||||
}
|
||||
|
||||
WritableMap imageInfo = saveToCameraRoll ? saveToMediaStore(image) : saveTempImageFile(image);
|
||||
if (imageInfo == null)
|
||||
promise.reject("CameraKit", "failed to save image to MediaStore");
|
||||
else {
|
||||
promise.resolve(imageInfo);
|
||||
CameraViewManager.reconnect();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private WritableMap createImageInfo(String fileUri, String id, String fileName, long fileSize, int width, int height) {
|
||||
WritableMap imageInfo = Arguments.createMap();
|
||||
imageInfo.putString("uri", fileUri);
|
||||
imageInfo.putString("id", id);
|
||||
imageInfo.putString("name", fileName);
|
||||
imageInfo.putInt("size", (int) fileSize);
|
||||
imageInfo.putInt("width", width);
|
||||
imageInfo.putInt("height", height);
|
||||
return imageInfo;
|
||||
}
|
||||
|
||||
private WritableMap saveToMediaStore(Bitmap image) {
|
||||
try {
|
||||
String fileUri = MediaStore.Images.Media.insertImage(context.getContentResolver(), image, System.currentTimeMillis() + "", "");
|
||||
Cursor cursor = context.getContentResolver().query(Uri.parse(fileUri), new String[]{
|
||||
MediaStore.Images.ImageColumns.DATA,
|
||||
MediaStore.Images.ImageColumns.DISPLAY_NAME
|
||||
}, null, null, null);
|
||||
cursor.moveToFirst();
|
||||
int pathIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATA);
|
||||
int nameIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DISPLAY_NAME);
|
||||
String filePath = cursor.getString(pathIndex);
|
||||
String fileName = cursor.getString(nameIndex);
|
||||
long fileSize = new File(filePath).length();
|
||||
cursor.close();
|
||||
|
||||
return createImageInfo(fileUri, filePath, fileName, fileSize, image.getWidth(), image.getHeight());
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap decodeAndRotateIfNeeded(byte[] rawImageData) {
|
||||
Matrix bitmapMatrix = getRotationMatrix(rawImageData);
|
||||
Bitmap image = BitmapFactory.decodeByteArray(rawImageData, 0, rawImageData.length);
|
||||
if (bitmapMatrix.isIdentity())
|
||||
return image;
|
||||
else
|
||||
return rotateImage(image, bitmapMatrix);
|
||||
}
|
||||
|
||||
private Bitmap rotateImage(Bitmap image, Matrix bitmapMatrix) {
|
||||
return Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), bitmapMatrix, false);
|
||||
}
|
||||
|
||||
private Matrix getRotationMatrix(byte[] rawImageData) {
|
||||
try {
|
||||
return tryGetRotationMatrix(rawImageData);
|
||||
} catch (Exception e) {
|
||||
return new Matrix();
|
||||
}
|
||||
}
|
||||
|
||||
private Matrix tryGetRotationMatrix(byte[] rawImageData) throws ImageProcessingException, IOException, MetadataException {
|
||||
Matrix matrix = new Matrix();
|
||||
Metadata metadata = readMetadata(rawImageData);
|
||||
final ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
|
||||
boolean hasOrientation = exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION);
|
||||
if (hasOrientation) {
|
||||
final int exifOrientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
|
||||
boolean isFacingFront = CameraViewManager.getCameraInfo().facing == Camera.CameraInfo.CAMERA_FACING_FRONT;
|
||||
convertExifOrientationToMatrix(matrix, exifOrientation, isFacingFront);
|
||||
}
|
||||
return matrix;
|
||||
}
|
||||
|
||||
private void convertExifOrientationToMatrix(Matrix matrix, int exifOrientation, boolean isCameraFacingFront) {
|
||||
switch (exifOrientation) {
|
||||
case 1:
|
||||
break; // top left
|
||||
case 2:
|
||||
matrix.postScale(-1, 1);
|
||||
break; // top right
|
||||
case 3:
|
||||
matrix.postRotate(180);
|
||||
break; // bottom right
|
||||
case 4:
|
||||
matrix.postRotate(180);
|
||||
matrix.postScale(-1, 1);
|
||||
break; // bottom left
|
||||
case 5:
|
||||
matrix.postRotate(90);
|
||||
matrix.postScale(-1, 1);
|
||||
break; // left top
|
||||
case 6:
|
||||
matrix.postRotate(90);
|
||||
break; // right top
|
||||
case 7:
|
||||
matrix.postRotate(270);
|
||||
matrix.postScale(-1, 1);
|
||||
break; // right bottom
|
||||
case 8:
|
||||
matrix.postRotate(270);
|
||||
break; // left bottom
|
||||
default:
|
||||
break; // Unknown
|
||||
}
|
||||
if (isCameraFacingFront) {
|
||||
matrix.postRotate(180);
|
||||
}
|
||||
}
|
||||
|
||||
private Metadata readMetadata(byte[] rawImageData) throws ImageProcessingException, IOException {
|
||||
Metadata metadata = null;
|
||||
ByteArrayInputStream inputStream = null;
|
||||
BufferedInputStream bufferedInputStream = null;
|
||||
try {
|
||||
inputStream = new ByteArrayInputStream(rawImageData);
|
||||
bufferedInputStream = new BufferedInputStream(inputStream);
|
||||
metadata = ImageMetadataReader.readMetadata(bufferedInputStream, rawImageData.length);
|
||||
} finally {
|
||||
if (bufferedInputStream != null) bufferedInputStream.close();
|
||||
if (inputStream != null) inputStream.close();
|
||||
}
|
||||
return metadata;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private WritableMap saveTempImageFile(Bitmap image) {
|
||||
File imageFile;
|
||||
FileOutputStream outputStream;
|
||||
|
||||
Long tsLong = System.currentTimeMillis()/1000;
|
||||
String fileName = "temp_Image_" + tsLong.toString() + ".jpg";
|
||||
|
||||
try {
|
||||
imageFile = new File(context.getCacheDir(), fileName);
|
||||
if (imageFile.exists()) {
|
||||
imageFile.delete();
|
||||
}
|
||||
outputStream = new FileOutputStream(imageFile);
|
||||
image.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Error accessing file: " + e.getMessage());
|
||||
imageFile = null;
|
||||
}
|
||||
return (imageFile != null) ? createImageInfo(Uri.fromFile(imageFile).toString(), imageFile.getAbsolutePath(), fileName, imageFile.length(), image.getWidth(), image.getHeight()) : null;
|
||||
}
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
package com.rncamerakit;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
// We're saving in shared preferences if a permission was requested since for some unknown reason,
|
||||
// activitycompat.shouldshowrequestpermissionrationale always returned false
|
||||
public class SharedPrefs {
|
||||
public static boolean getBoolean(Context context, String key) {
|
||||
return prefs(context).getBoolean(key, false);
|
||||
}
|
||||
|
||||
public static void putBoolean(Context context, String key, boolean value) {
|
||||
prefs(context).edit().putBoolean(key, value).apply();
|
||||
}
|
||||
|
||||
private static SharedPreferences prefs(Context context) {
|
||||
return context.getSharedPreferences("RN_CAMERA_KIT", Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,224 +0,0 @@
|
||||
package com.rncamerakit;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class Utils {
|
||||
|
||||
private final static String CONTENT_PREFIX = "content://";
|
||||
public final static String FILE_PREFIX = "file://";
|
||||
private static final int MAX_SAMPLE_SIZE = 8;
|
||||
private static final float MAX_SCREEN_RATIO = 16 / 9f;
|
||||
|
||||
@Nullable
|
||||
public static String getStringSafe(ReadableMap map, String key) {
|
||||
if (map.hasKey(key)) {
|
||||
return map.getString(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static @Nullable Integer getIntSafe(ReadableMap map, String key) {
|
||||
if (map.hasKey(key)) {
|
||||
return map.getInt(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static @Nullable Boolean getBooleanSafe(ReadableMap map, String key) {
|
||||
if (map.hasKey(key)) {
|
||||
return map.getBoolean(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static @NonNull ArrayList<String> readableArrayToList(ReadableArray items) {
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
for(int i = 0; i < items.size(); i++) {
|
||||
list.add(items.getString(i));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
public static WritableMap resizeImage(Context context, ReadableMap image, String imageUrlString, int maxResolution, int compressionQuality) throws IOException {
|
||||
Bitmap sourceImage;
|
||||
sourceImage = Utils.loadBitmapFromFile(context, imageUrlString, maxResolution, maxResolution);
|
||||
|
||||
if (sourceImage == null) {
|
||||
throw new IOException("Unable to load source image from path");
|
||||
}
|
||||
|
||||
Bitmap scaledImage = Utils.resizeImage(sourceImage, maxResolution, maxResolution);
|
||||
if (sourceImage != scaledImage) {
|
||||
sourceImage.recycle();
|
||||
}
|
||||
|
||||
// Save the resulting image
|
||||
File path = context.getCacheDir();
|
||||
String resizedImagePath = Utils.saveImage(scaledImage, path, Long.toString(new Date().getTime()), Bitmap.CompressFormat.JPEG, compressionQuality);
|
||||
|
||||
// Clean up remaining image
|
||||
scaledImage.recycle();
|
||||
|
||||
WritableMap ans = Arguments.createMap();
|
||||
ans.merge(image);
|
||||
ans.putString("uri", FILE_PREFIX+resizedImagePath);
|
||||
ans.putInt("size", (int)new File(resizedImagePath).length());
|
||||
ans.putInt("width", scaledImage.getWidth());
|
||||
ans.putInt("height", scaledImage.getHeight());
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resize the specified bitmap, keeping its aspect ratio.
|
||||
*/
|
||||
private static Bitmap resizeImage(Bitmap image, int maxWidth, int maxHeight) {
|
||||
Bitmap newImage = null;
|
||||
if (image == null) {
|
||||
return null; // Can't load the image from the given path.
|
||||
}
|
||||
|
||||
if (maxHeight > 0 && maxWidth > 0) {
|
||||
float width = image.getWidth();
|
||||
float height = image.getHeight();
|
||||
|
||||
float ratio = Math.min((float)maxWidth / width, (float)maxHeight / height);
|
||||
|
||||
int finalWidth = (int) (width * ratio);
|
||||
int finalHeight = (int) (height * ratio);
|
||||
newImage = Bitmap.createScaledBitmap(image, finalWidth, finalHeight, true);
|
||||
}
|
||||
|
||||
return newImage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compute the inSampleSize value to use to load a bitmap.
|
||||
* Adapted from https://developer.android.com/training/displaying-bitmaps/load-bitmap.html
|
||||
*/
|
||||
public static int calculateInSampleSize(int width, int height, int reqWidth, int reqHeight) {
|
||||
|
||||
if (reqHeight == 0 || reqWidth == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int inSampleSize = 1;
|
||||
|
||||
if (height > reqHeight || width > reqWidth) {
|
||||
|
||||
final int halfHeight = height / 2;
|
||||
final int halfWidth = width / 2;
|
||||
|
||||
while (inSampleSize <= MAX_SAMPLE_SIZE
|
||||
&& (halfHeight / inSampleSize) >= reqHeight
|
||||
&& (halfWidth / inSampleSize) >= reqWidth) {
|
||||
inSampleSize *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return inSampleSize;
|
||||
}
|
||||
|
||||
private static Bitmap loadBitmap(Context context, String imagePath, BitmapFactory.Options options) throws IOException {
|
||||
Bitmap sourceImage = null;
|
||||
if (!imagePath.startsWith(CONTENT_PREFIX)) {
|
||||
try {
|
||||
sourceImage = BitmapFactory.decodeFile(imagePath, options);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
throw new IOException("Error decoding image file");
|
||||
}
|
||||
} else {
|
||||
ContentResolver cr = context.getContentResolver();
|
||||
InputStream input = cr.openInputStream(Uri.parse(imagePath));
|
||||
if (input != null) {
|
||||
sourceImage = BitmapFactory.decodeStream(input, null, options);
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
return sourceImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the bitmap resource from the file specified in imagePath.
|
||||
*/
|
||||
private static Bitmap loadBitmapFromFile(Context context, String imagePath, int newWidth,
|
||||
int newHeight) throws IOException {
|
||||
// Decode the image bounds to find the size of the source image.
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
loadBitmap(context, imagePath, options);
|
||||
|
||||
// Set a sample size according to the image size to lower memory usage.
|
||||
options.inSampleSize = calculateInSampleSize(options.outWidth, options.outHeight , newWidth, newHeight);
|
||||
options.inJustDecodeBounds = false;
|
||||
return loadBitmap(context, imagePath, options);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save the given bitmap in a directory. Extension is automatically generated using the bitmap format.
|
||||
*/
|
||||
private static String saveImage(Bitmap bitmap, File saveDirectory, String fileName,
|
||||
Bitmap.CompressFormat compressFormat, int quality)
|
||||
throws IOException {
|
||||
if (bitmap == null) {
|
||||
throw new IOException("The bitmap couldn't be resized");
|
||||
}
|
||||
|
||||
File newFile = new File(saveDirectory, fileName + "." + compressFormat.name());
|
||||
if(!newFile.createNewFile()) {
|
||||
throw new IOException("The file already exists");
|
||||
}
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
bitmap.compress(compressFormat, quality, outputStream);
|
||||
byte[] bitmapData = outputStream.toByteArray();
|
||||
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(newFile);
|
||||
fos.write(bitmapData);
|
||||
fos.flush();
|
||||
fos.close();
|
||||
|
||||
return newFile.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Since Camera API 1 doesn't support the new 18:9 and 18.5:9 screen aspect ratio, we convert to the
|
||||
* max supported aspect ratio - 16:9
|
||||
*/
|
||||
public static int convertDeviceHeightToSupportedAspectRatio(float actualWidth, float actualHeight) {
|
||||
return (int) (actualHeight / actualWidth > MAX_SCREEN_RATIO ? actualWidth * MAX_SCREEN_RATIO : actualHeight);
|
||||
}
|
||||
|
||||
|
||||
public static void runOnWorkerThread(Runnable runnable) {
|
||||
new Thread(runnable).start();
|
||||
}
|
||||
}
|
||||
@ -1,116 +0,0 @@
|
||||
package com.rncamerakit.camera;
|
||||
|
||||
import android.hardware.Camera;
|
||||
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.rncamerakit.camera.commands.Capture;
|
||||
import com.rncamerakit.camera.permission.CameraPermission;
|
||||
|
||||
|
||||
public class CameraModule extends ReactContextBaseJavaModule {
|
||||
|
||||
private final CameraPermission cameraPermission;
|
||||
private Promise checkPermissionStatusPromise;
|
||||
|
||||
public CameraModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
cameraPermission = new CameraPermission();
|
||||
checkPermissionWhenActivityIsAvailable();
|
||||
}
|
||||
|
||||
private void checkPermissionWhenActivityIsAvailable() {
|
||||
getReactApplicationContext().addLifecycleEventListener(new LifecycleEventListener() {
|
||||
@Override
|
||||
public void onHostResume() {
|
||||
if (checkPermissionStatusPromise != null && getCurrentActivity() != null) {
|
||||
getCurrentActivity().runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
checkPermissionStatusPromise.resolve(cameraPermission.checkAuthorizationStatus(getCurrentActivity()));
|
||||
checkPermissionStatusPromise = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostPause() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostDestroy() {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "RNKitCameraModule";
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void checkDeviceCameraAuthorizationStatus(Promise promise) {
|
||||
if (getCurrentActivity() == null) {
|
||||
checkPermissionStatusPromise = promise;
|
||||
} else {
|
||||
promise.resolve(cameraPermission.checkAuthorizationStatus(getCurrentActivity()));
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void requestDeviceCameraAuthorization(Promise promise) {
|
||||
cameraPermission.requestAccess(getCurrentActivity(), promise);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void hasFrontCamera(Promise promise) {
|
||||
|
||||
int numCameras = Camera.getNumberOfCameras();
|
||||
for (int i = 0; i < numCameras; i++) {
|
||||
Camera.CameraInfo info = new Camera.CameraInfo();
|
||||
Camera.getCameraInfo(i, info);
|
||||
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||
promise.resolve(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
promise.resolve(false);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void hasFlashForCurrentCamera(Promise promise) {
|
||||
Camera camera = CameraViewManager.getCamera();
|
||||
promise.resolve(camera.getParameters().getSupportedFlashModes() != null);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void changeCamera(Promise promise) {
|
||||
promise.resolve(CameraViewManager.changeCamera());
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void setFlashMode(String mode, Promise promise) {
|
||||
promise.resolve(CameraViewManager.setFlashMode(mode));
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getFlashMode(Promise promise) {
|
||||
Camera camera = CameraViewManager.getCamera();
|
||||
promise.resolve(camera.getParameters().getFlashMode());
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void capture(boolean saveToCameraRoll, final Promise promise) {
|
||||
new Capture(getReactApplicationContext(), saveToCameraRoll).execute(promise);
|
||||
}
|
||||
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
cameraPermission.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
@ -1,141 +0,0 @@
|
||||
package com.rncamerakit.camera;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import androidx.annotation.ColorInt;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.rncamerakit.Utils;
|
||||
import com.rncamerakit.camera.barcode.BarcodeFrame;
|
||||
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
|
||||
public class CameraView extends FrameLayout implements SurfaceHolder.Callback {
|
||||
private SurfaceView surface;
|
||||
|
||||
private boolean showFrame;
|
||||
private Rect frameRect;
|
||||
private BarcodeFrame barcodeFrame;
|
||||
@ColorInt private int frameColor = Color.GREEN;
|
||||
@ColorInt private int laserColor = Color.RED;
|
||||
|
||||
public CameraView(ThemedReactContext context) {
|
||||
super(context);
|
||||
surface = new SurfaceView(context);
|
||||
setBackgroundColor(Color.BLACK);
|
||||
addView(surface, MATCH_PARENT, MATCH_PARENT);
|
||||
surface.getHolder().addCallback(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
int actualPreviewWidth = getResources().getDisplayMetrics().widthPixels;
|
||||
int actualPreviewHeight = getResources().getDisplayMetrics().heightPixels;
|
||||
int height = Utils.convertDeviceHeightToSupportedAspectRatio(actualPreviewWidth, actualPreviewHeight);
|
||||
surface.layout(0, 0, actualPreviewWidth, height);
|
||||
if (barcodeFrame != null) {
|
||||
((View) barcodeFrame).layout(0, 0, actualPreviewWidth, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
CameraViewManager.setCameraView(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
CameraViewManager.setCameraView(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
CameraViewManager.removeCameraView();
|
||||
}
|
||||
|
||||
|
||||
public SurfaceHolder getHolder() {
|
||||
return surface.getHolder();
|
||||
}
|
||||
|
||||
private final Runnable measureAndLayout = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
measure(
|
||||
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
|
||||
layout(getLeft(), getTop(), getRight(), getBottom());
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void requestLayout() {
|
||||
super.requestLayout();
|
||||
post(measureAndLayout);
|
||||
}
|
||||
|
||||
public void setShowFrame(boolean showFrame) {
|
||||
this.showFrame = showFrame;
|
||||
}
|
||||
|
||||
public void showFrame() {
|
||||
if (showFrame) {
|
||||
barcodeFrame = new BarcodeFrame(getContext());
|
||||
barcodeFrame.setFrameColor(frameColor);
|
||||
barcodeFrame.setLaserColor(laserColor);
|
||||
addView(barcodeFrame);
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public Rect getFramingRectInPreview(int previewWidth, int previewHeight) {
|
||||
if (frameRect == null) {
|
||||
if (barcodeFrame != null) {
|
||||
Rect framingRect = new Rect(barcodeFrame.getFrameRect());
|
||||
int frameWidth = barcodeFrame.getWidth();
|
||||
int frameHeight = barcodeFrame.getHeight();
|
||||
|
||||
if (previewWidth < frameWidth) {
|
||||
framingRect.left = framingRect.left * previewWidth / frameWidth;
|
||||
framingRect.right = framingRect.right * previewWidth / frameWidth;
|
||||
}
|
||||
if (previewHeight < frameHeight) {
|
||||
framingRect.top = framingRect.top * previewHeight / frameHeight;
|
||||
framingRect.bottom = framingRect.bottom * previewHeight / frameHeight;
|
||||
}
|
||||
|
||||
frameRect = framingRect;
|
||||
} else {
|
||||
frameRect = new Rect(0, 0, previewWidth, previewHeight);
|
||||
}
|
||||
}
|
||||
return frameRect;
|
||||
}
|
||||
|
||||
public void setFrameColor(@ColorInt int color) {
|
||||
this.frameColor = color;
|
||||
if (barcodeFrame != null) {
|
||||
barcodeFrame.setFrameColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
public void setLaserColor(@ColorInt int color) {
|
||||
this.laserColor = color;
|
||||
if (barcodeFrame != null) {
|
||||
barcodeFrame.setLaserColor(laserColor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set background color for Surface view on the period, while camera is not loaded yet.
|
||||
* Provides opportunity for user to hide period while camera is loading
|
||||
* @param color - color of the surfaceview
|
||||
*/
|
||||
public void setSurfaceBgColor(@ColorInt int color) {
|
||||
surface.setBackgroundColor(color);
|
||||
}
|
||||
}
|
||||
@ -1,339 +0,0 @@
|
||||
package com.rncamerakit.camera;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.Camera;
|
||||
import android.hardware.SensorManager;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.IntRange;
|
||||
|
||||
import android.view.Display;
|
||||
import android.view.OrientationEventListener;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.uimanager.SimpleViewManager;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.google.zxing.Result;
|
||||
import com.rncamerakit.Utils;
|
||||
import com.rncamerakit.camera.barcode.BarcodeScanner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.rncamerakit.camera.Orientation.getSupportedRotation;
|
||||
|
||||
@SuppressWarnings("MagicNumber deprecation")
|
||||
// We're still using Camera API 1, everything is deprecated
|
||||
public class CameraViewManager extends SimpleViewManager<CameraView> {
|
||||
|
||||
private static Camera camera = null;
|
||||
private static int currentCamera = 0;
|
||||
private static String flashMode = Camera.Parameters.FLASH_MODE_AUTO;
|
||||
private static Stack<CameraView> cameraViews = new Stack<>();
|
||||
private static ThemedReactContext reactContext;
|
||||
private static OrientationEventListener orientationListener;
|
||||
private static int currentRotation = 0;
|
||||
private static AtomicBoolean cameraReleased = new AtomicBoolean(false);
|
||||
|
||||
private static boolean shouldScan = false;
|
||||
|
||||
private static BarcodeScanner scanner;
|
||||
private static Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
|
||||
@Override
|
||||
public void onPreviewFrame(final byte[] data, final Camera camera) {
|
||||
Utils.runOnWorkerThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (scanner != null) {
|
||||
scanner.onPreviewFrame(data, camera);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
public static Camera getCamera() {
|
||||
return camera;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "CameraView";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CameraView createViewInstance(ThemedReactContext reactContext) {
|
||||
CameraViewManager.reactContext = reactContext;
|
||||
return new CameraView(reactContext);
|
||||
}
|
||||
|
||||
static void setCameraView(CameraView cameraView) {
|
||||
if (!cameraViews.isEmpty() && cameraViews.peek() == cameraView) return;
|
||||
CameraViewManager.cameraViews.push(cameraView);
|
||||
connectHolder();
|
||||
createOrientationListener();
|
||||
}
|
||||
|
||||
private static void createOrientationListener() {
|
||||
if (orientationListener != null) return;
|
||||
orientationListener = new OrientationEventListener(reactContext, SensorManager.SENSOR_DELAY_NORMAL) {
|
||||
@Override
|
||||
public void onOrientationChanged(@IntRange(from = -1, to = 359) int angle) {
|
||||
if (angle == OrientationEventListener.ORIENTATION_UNKNOWN) return;
|
||||
setCameraRotation(359 - angle, false);
|
||||
}
|
||||
};
|
||||
orientationListener.enable();
|
||||
}
|
||||
|
||||
static boolean setFlashMode(String mode) {
|
||||
if (camera == null) {
|
||||
return false;
|
||||
}
|
||||
Camera.Parameters parameters = camera.getParameters();
|
||||
List supportedModes = parameters.getSupportedFlashModes();
|
||||
if (supportedModes != null && supportedModes.contains(mode)) {
|
||||
flashMode = mode;
|
||||
parameters.setFlashMode(flashMode);
|
||||
camera.setParameters(parameters);
|
||||
camera.startPreview();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static boolean changeCamera() {
|
||||
if (Camera.getNumberOfCameras() == 1) {
|
||||
return false;
|
||||
}
|
||||
currentCamera++;
|
||||
currentCamera = currentCamera % Camera.getNumberOfCameras();
|
||||
initCamera();
|
||||
connectHolder();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void initCamera() {
|
||||
if (camera != null) {
|
||||
releaseCamera();
|
||||
}
|
||||
try {
|
||||
camera = Camera.open(currentCamera);
|
||||
updateCameraSize();
|
||||
cameraReleased.set(false);
|
||||
setCameraRotation(currentRotation, true);
|
||||
} catch (RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
setBarcodeScanner();
|
||||
}
|
||||
|
||||
private static void releaseCamera() {
|
||||
camera.setOneShotPreviewCallback(null);
|
||||
cameraReleased.set(true);
|
||||
camera.release();
|
||||
}
|
||||
|
||||
private static void connectHolder() {
|
||||
if (cameraViews.isEmpty() || cameraViews.peek().getHolder() == null) return;
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (camera == null) {
|
||||
initCamera();
|
||||
}
|
||||
|
||||
if (cameraViews.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
cameraViews.peek().post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
camera.stopPreview();
|
||||
camera.setPreviewDisplay(cameraViews.peek().getHolder());
|
||||
camera.startPreview();
|
||||
if (shouldScan) {
|
||||
camera.setOneShotPreviewCallback(previewCallback);
|
||||
}
|
||||
cameraViews.peek().setSurfaceBgColor(Color.TRANSPARENT);
|
||||
cameraViews.peek().showFrame();
|
||||
} catch (IOException | RuntimeException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
static void removeCameraView() {
|
||||
if (!cameraViews.isEmpty()) {
|
||||
cameraViews.pop();
|
||||
}
|
||||
if (!cameraViews.isEmpty()) {
|
||||
connectHolder();
|
||||
} else if (camera != null) {
|
||||
releaseCamera();
|
||||
camera = null;
|
||||
}
|
||||
if (cameraViews.isEmpty()) {
|
||||
clearOrientationListener();
|
||||
}
|
||||
}
|
||||
|
||||
private static void clearOrientationListener() {
|
||||
if (orientationListener != null) {
|
||||
orientationListener.disable();
|
||||
orientationListener = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void setCameraRotation(int rotation, boolean force) {
|
||||
if (camera == null) return;
|
||||
int supportedRotation = getSupportedRotation(rotation);
|
||||
if (supportedRotation == currentRotation && !force) return;
|
||||
currentRotation = supportedRotation;
|
||||
|
||||
if (cameraReleased.get()) return;
|
||||
Camera.Parameters parameters = camera.getParameters();
|
||||
parameters.setRotation(supportedRotation);
|
||||
parameters.setPictureFormat(PixelFormat.JPEG);
|
||||
camera.setDisplayOrientation(Orientation.getDeviceOrientation(reactContext.getCurrentActivity()));
|
||||
camera.setParameters(parameters);
|
||||
}
|
||||
|
||||
public static Camera.CameraInfo getCameraInfo() {
|
||||
Camera.CameraInfo info = new Camera.CameraInfo();
|
||||
Camera.getCameraInfo(currentCamera, info);
|
||||
return info;
|
||||
}
|
||||
|
||||
private static Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
|
||||
final double ASPECT_TOLERANCE = 0.15;
|
||||
double targetRatio = (double) h / w;
|
||||
if (sizes == null) return null;
|
||||
Camera.Size optimalSize = null;
|
||||
double minDiff = Double.MAX_VALUE;
|
||||
for (Camera.Size size : sizes) {
|
||||
double ratio = (double) size.width / size.height;
|
||||
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
|
||||
if (Math.abs(size.height - h) < minDiff) {
|
||||
optimalSize = size;
|
||||
minDiff = Math.abs(size.height - h);
|
||||
}
|
||||
}
|
||||
if (optimalSize == null) {
|
||||
minDiff = Double.MAX_VALUE;
|
||||
for (Camera.Size size : sizes) {
|
||||
if (Math.abs(size.height - h) < minDiff) {
|
||||
optimalSize = size;
|
||||
minDiff = Math.abs(size.height - h);
|
||||
}
|
||||
}
|
||||
}
|
||||
return optimalSize;
|
||||
}
|
||||
|
||||
private static void updateCameraSize() {
|
||||
try {
|
||||
Camera camera = CameraViewManager.getCamera();
|
||||
|
||||
WindowManager wm = (WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE);
|
||||
Display display = wm.getDefaultDisplay();
|
||||
Point size = new Point();
|
||||
display.getSize(size);
|
||||
size.y = Utils.convertDeviceHeightToSupportedAspectRatio(size.x, size.y);
|
||||
if (camera == null) return;
|
||||
List<Camera.Size> supportedPreviewSizes = camera.getParameters().getSupportedPreviewSizes();
|
||||
List<Camera.Size> supportedPictureSizes = camera.getParameters().getSupportedPictureSizes();
|
||||
Camera.Size optimalSize = getOptimalPreviewSize(supportedPreviewSizes, size.x, size.y);
|
||||
Camera.Size optimalPictureSize = getOptimalPreviewSize(supportedPictureSizes, size.x, size.y);
|
||||
Camera.Parameters parameters = camera.getParameters();
|
||||
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
|
||||
parameters.setPreviewSize(optimalSize.width, optimalSize.height);
|
||||
parameters.setPictureSize(optimalPictureSize.width, optimalPictureSize.height);
|
||||
parameters.setFlashMode(flashMode);
|
||||
camera.setParameters(parameters);
|
||||
} catch (RuntimeException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public static void reconnect() {
|
||||
connectHolder();
|
||||
}
|
||||
|
||||
public static int getRotationCount() {
|
||||
return currentRotation / 90;
|
||||
}
|
||||
|
||||
public static void setBarcodeScanner() {
|
||||
scanner = new BarcodeScanner(previewCallback, new BarcodeScanner.ResultHandler() {
|
||||
@Override
|
||||
public void handleResult(Result result) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putString("codeStringValue", result.getText());
|
||||
if (!cameraViews.empty())
|
||||
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(cameraViews.peek().getId(), "onReadCode", event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
|
||||
return MapBuilder.<String, Object>builder()
|
||||
.put("onReadCode",
|
||||
MapBuilder.of("registrationName", "onReadCode"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@ReactProp(name = "scanBarcode")
|
||||
public void setShouldScan(CameraView view, boolean scanBarcode) {
|
||||
shouldScan = scanBarcode;
|
||||
if (shouldScan && camera != null) {
|
||||
camera.setOneShotPreviewCallback(previewCallback);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "showFrame", defaultBoolean = false)
|
||||
public void setFrame(CameraView view, boolean show) {
|
||||
view.setShowFrame(show);
|
||||
}
|
||||
|
||||
@ReactProp(name = "frameColor", defaultInt = Color.GREEN)
|
||||
public void setFrameColor(CameraView view, @ColorInt int color) {
|
||||
view.setFrameColor(color);
|
||||
}
|
||||
|
||||
@ReactProp(name = "laserColor", defaultInt = Color.RED)
|
||||
public void setLaserColor(CameraView view, @ColorInt int color) {
|
||||
view.setLaserColor(color);
|
||||
}
|
||||
|
||||
@ReactProp(name = "surfaceColor")
|
||||
public void setSurfaceBackground(CameraView view, @ColorInt int color) {
|
||||
view.setSurfaceBgColor(color);
|
||||
}
|
||||
|
||||
public static synchronized Rect getFramingRectInPreview(int previewWidth, int previewHeight) {
|
||||
return cameraViews.peek().getFramingRectInPreview(previewWidth, previewHeight);
|
||||
}
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
package com.rncamerakit.camera;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.hardware.Camera;
|
||||
import android.view.Surface;
|
||||
|
||||
import com.rncamerakit.DeviceUtils;
|
||||
|
||||
import static com.rncamerakit.camera.CameraViewManager.getCameraInfo;
|
||||
|
||||
@SuppressWarnings({"MagicNumber", "deprecation"})
|
||||
class Orientation {
|
||||
private static final int PORTRAIT_ROTATION = 90;
|
||||
|
||||
static int getDeviceOrientation(Activity activity) {
|
||||
if (activity == null) return PORTRAIT_ROTATION;
|
||||
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
|
||||
Camera.CameraInfo info = getCameraInfo();
|
||||
int degrees = 0;
|
||||
switch (rotation) {
|
||||
case Surface.ROTATION_0: degrees = 0; break;
|
||||
case Surface.ROTATION_90: degrees = 90; break;
|
||||
case Surface.ROTATION_180: degrees = 180; break;
|
||||
case Surface.ROTATION_270: degrees = 270; break;
|
||||
}
|
||||
|
||||
int result;
|
||||
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
|
||||
result = (info.orientation + degrees) % 360;
|
||||
result = (360 - result) % 360; // compensate the mirror
|
||||
} else { // back-facing
|
||||
result = (info.orientation - degrees + 360) % 360;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int getSupportedRotation(int rotation) {
|
||||
int degrees = convertRotationToSupportedAxis(rotation);
|
||||
return isFrontFacingCamera() ? adaptFrontCamera(degrees) : adaptBackCamera(degrees);
|
||||
}
|
||||
|
||||
private static int convertRotationToSupportedAxis(int rotation) {
|
||||
if (rotation < 45) {
|
||||
return 0;
|
||||
} else if (rotation < 135) {
|
||||
return 90;
|
||||
} else if (rotation < 225) {
|
||||
return 180;
|
||||
} else if (rotation < 315){
|
||||
return 270;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static boolean isFrontFacingCamera() {
|
||||
return getCameraInfo().facing == Camera.CameraInfo.CAMERA_FACING_FRONT;
|
||||
}
|
||||
|
||||
private static int adaptBackCamera(int degrees) {
|
||||
return (getCameraInfo().orientation - degrees + 360) % 360;
|
||||
}
|
||||
|
||||
private static int adaptFrontCamera(int degrees) {
|
||||
if (DeviceUtils.isGoogleDevice()) {
|
||||
int result = (getCameraInfo().orientation + degrees) % 360;
|
||||
result = (result) % 360; // compensate the mirror
|
||||
return result;
|
||||
} else {
|
||||
int result = (getCameraInfo().orientation + degrees + 180) % 360;
|
||||
result = (result) % 360; // compensate the mirror
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,111 +0,0 @@
|
||||
package com.rncamerakit.camera.barcode;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffXfermode;
|
||||
import android.graphics.Rect;
|
||||
import androidx.annotation.ColorInt;
|
||||
import android.view.View;
|
||||
|
||||
|
||||
import com.rncamerakit.R;
|
||||
|
||||
public class BarcodeFrame extends View {
|
||||
|
||||
private static final int STROKE_WIDTH = 5;
|
||||
private static final int ANIMATION_SPEED = 8;
|
||||
private static final int WIDTH_SCALE = 7;
|
||||
private static final double HEIGHT_SCALE = 2.75;
|
||||
|
||||
private Paint dimPaint;
|
||||
private Paint framePaint;
|
||||
private Paint borderPaint;
|
||||
private Paint laserPaint;
|
||||
private Rect frameRect;
|
||||
private int width;
|
||||
private int height;
|
||||
private int borderMargin;
|
||||
|
||||
private long previousFrameTime = System.currentTimeMillis();
|
||||
private int laserY;
|
||||
|
||||
public BarcodeFrame(Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
framePaint = new Paint();
|
||||
framePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
|
||||
dimPaint = new Paint();
|
||||
dimPaint.setStyle(Paint.Style.FILL);
|
||||
dimPaint.setColor(context.getResources().getColor(R.color.bg_dark));
|
||||
borderPaint = new Paint();
|
||||
borderPaint.setStyle(Paint.Style.STROKE);
|
||||
borderPaint.setStrokeWidth(STROKE_WIDTH);
|
||||
laserPaint = new Paint();
|
||||
laserPaint.setStyle(Paint.Style.STROKE);
|
||||
laserPaint.setStrokeWidth(STROKE_WIDTH);
|
||||
|
||||
frameRect = new Rect();
|
||||
borderMargin = context.getResources().getDimensionPixelSize(R.dimen.border_length);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
width = getMeasuredWidth();
|
||||
height = getMeasuredHeight();
|
||||
int marginWidth = width / WIDTH_SCALE;
|
||||
int marginHeight = (int) (height / HEIGHT_SCALE);
|
||||
|
||||
frameRect.left = marginWidth;
|
||||
frameRect.right = width - marginWidth;
|
||||
frameRect.top = marginHeight;
|
||||
frameRect.bottom = height - marginHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
long timeElapsed = (System.currentTimeMillis() - previousFrameTime);
|
||||
super.onDraw(canvas);
|
||||
canvas.drawRect(0, 0, width, height, dimPaint);
|
||||
canvas.drawRect(frameRect, framePaint);
|
||||
drawBorder(canvas);
|
||||
drawLaser(canvas, timeElapsed);
|
||||
previousFrameTime = System.currentTimeMillis();
|
||||
this.invalidate(frameRect);
|
||||
}
|
||||
|
||||
private void drawBorder(Canvas canvas) {
|
||||
canvas.drawLine(frameRect.left, frameRect.top, frameRect.left, frameRect.top + borderMargin, borderPaint);
|
||||
canvas.drawLine(frameRect.left, frameRect.top, frameRect.left + borderMargin, frameRect.top, borderPaint);
|
||||
canvas.drawLine(frameRect.left, frameRect.bottom, frameRect.left, frameRect.bottom - borderMargin, borderPaint);
|
||||
canvas.drawLine(frameRect.left, frameRect.bottom, frameRect.left + borderMargin, frameRect.bottom, borderPaint);
|
||||
canvas.drawLine(frameRect.right, frameRect.top, frameRect.right - borderMargin, frameRect.top, borderPaint);
|
||||
canvas.drawLine(frameRect.right, frameRect.top, frameRect.right, frameRect.top + borderMargin, borderPaint);
|
||||
canvas.drawLine(frameRect.right, frameRect.bottom, frameRect.right, frameRect.bottom - borderMargin, borderPaint);
|
||||
canvas.drawLine(frameRect.right, frameRect.bottom, frameRect.right - borderMargin, frameRect.bottom, borderPaint);
|
||||
}
|
||||
|
||||
private void drawLaser(Canvas canvas, long timeElapsed) {
|
||||
if (laserY > frameRect.bottom || laserY < frameRect.top) laserY = frameRect.top;
|
||||
canvas.drawLine(frameRect.left + STROKE_WIDTH, laserY, frameRect.right - STROKE_WIDTH, laserY, laserPaint);
|
||||
laserY += (timeElapsed) / ANIMATION_SPEED;
|
||||
}
|
||||
|
||||
public Rect getFrameRect() {
|
||||
return frameRect;
|
||||
}
|
||||
|
||||
public void setFrameColor(@ColorInt int borderColor) {
|
||||
borderPaint.setColor(borderColor);
|
||||
}
|
||||
|
||||
public void setLaserColor(@ColorInt int laserColor) {
|
||||
laserPaint.setColor(laserColor);
|
||||
}
|
||||
}
|
||||
@ -1,145 +0,0 @@
|
||||
package com.rncamerakit.camera.barcode;
|
||||
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.hardware.Camera;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.LuminanceSource;
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
import com.rncamerakit.camera.CameraViewManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class BarcodeScanner {
|
||||
|
||||
public interface ResultHandler {
|
||||
void handleResult(Result result);
|
||||
}
|
||||
|
||||
private MultiFormatReader mMultiFormatReader;
|
||||
private static final List<BarcodeFormat> ALL_FORMATS = new ArrayList<>();
|
||||
private ResultHandler resultHandler;
|
||||
|
||||
private Camera.PreviewCallback previewCallback;
|
||||
|
||||
static {
|
||||
ALL_FORMATS.add(BarcodeFormat.AZTEC);
|
||||
ALL_FORMATS.add(BarcodeFormat.CODABAR);
|
||||
ALL_FORMATS.add(BarcodeFormat.CODE_39);
|
||||
ALL_FORMATS.add(BarcodeFormat.CODE_93);
|
||||
ALL_FORMATS.add(BarcodeFormat.CODE_128);
|
||||
ALL_FORMATS.add(BarcodeFormat.DATA_MATRIX);
|
||||
ALL_FORMATS.add(BarcodeFormat.EAN_8);
|
||||
ALL_FORMATS.add(BarcodeFormat.EAN_13);
|
||||
ALL_FORMATS.add(BarcodeFormat.ITF);
|
||||
ALL_FORMATS.add(BarcodeFormat.MAXICODE);
|
||||
ALL_FORMATS.add(BarcodeFormat.PDF_417);
|
||||
ALL_FORMATS.add(BarcodeFormat.QR_CODE);
|
||||
ALL_FORMATS.add(BarcodeFormat.RSS_14);
|
||||
ALL_FORMATS.add(BarcodeFormat.RSS_EXPANDED);
|
||||
ALL_FORMATS.add(BarcodeFormat.UPC_A);
|
||||
ALL_FORMATS.add(BarcodeFormat.UPC_E);
|
||||
ALL_FORMATS.add(BarcodeFormat.UPC_EAN_EXTENSION);
|
||||
}
|
||||
|
||||
public BarcodeScanner(@NonNull Camera.PreviewCallback previewCallback, @NonNull ResultHandler resultHandler) {
|
||||
Map<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
|
||||
hints.put(DecodeHintType.POSSIBLE_FORMATS, ALL_FORMATS);
|
||||
mMultiFormatReader = new MultiFormatReader();
|
||||
mMultiFormatReader.setHints(hints);
|
||||
|
||||
this.previewCallback = previewCallback;
|
||||
this.resultHandler = resultHandler;
|
||||
}
|
||||
|
||||
public void onPreviewFrame(byte[] data, final Camera camera) {
|
||||
try {
|
||||
Camera.Size size = camera.getParameters().getPreviewSize();
|
||||
int width = size.width;
|
||||
int height = size.height;
|
||||
|
||||
int tmp = width;
|
||||
width = height;
|
||||
height = tmp;
|
||||
data = getRotatedData(data, camera);
|
||||
|
||||
final Result result = decodeResult(getLuminanceSource(data, width, height));
|
||||
|
||||
if (result != null) {
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
resultHandler.handleResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
camera.setOneShotPreviewCallback(previewCallback);
|
||||
} catch (RuntimeException e) {
|
||||
Log.w("CameraKit", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Result decodeResult(LuminanceSource source) {
|
||||
Result rawResult = null;
|
||||
if (source != null) {
|
||||
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||
try {
|
||||
rawResult = mMultiFormatReader.decodeWithState(bitmap);
|
||||
} catch (ReaderException ignored) {
|
||||
} finally {
|
||||
mMultiFormatReader.reset();
|
||||
}
|
||||
|
||||
if (rawResult == null && source.isRotateSupported()) {
|
||||
LuminanceSource rotatedSource = source.rotateCounterClockwise();
|
||||
bitmap = new BinaryBitmap(new HybridBinarizer(rotatedSource));
|
||||
try {
|
||||
rawResult = mMultiFormatReader.decodeWithState(bitmap);
|
||||
} catch (ReaderException ignored) {
|
||||
} finally {
|
||||
mMultiFormatReader.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
return rawResult;
|
||||
}
|
||||
|
||||
private LuminanceSource getLuminanceSource(byte[] data, int width, int height) {
|
||||
Rect rect = CameraViewManager.getFramingRectInPreview(width, height);
|
||||
try {
|
||||
return new RotateLuminanceSource(data, width, height, rect.left, rect.top,
|
||||
rect.width(), rect.height(), false);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte[] getRotatedData(byte[] data, Camera camera) {
|
||||
Camera.Size size = camera.getParameters().getPreviewSize();
|
||||
int width = size.width;
|
||||
int height = size.height;
|
||||
|
||||
byte[] rotatedData = new byte[data.length];
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++)
|
||||
rotatedData[x * height + height - y - 1] = data[x + y * width];
|
||||
}
|
||||
return rotatedData;
|
||||
}
|
||||
}
|
||||
@ -1,164 +0,0 @@
|
||||
package com.rncamerakit.camera.barcode;
|
||||
|
||||
import com.google.zxing.LuminanceSource;
|
||||
|
||||
/**
|
||||
* This class is mostly copy of {@link com.google.zxing.PlanarYUVLuminanceSource} class.
|
||||
* The only difference is adding {@code rotateCounterClockwise()} method
|
||||
* It was copied due to PlanarYUVLuminanceSource being final, so it can not be extended
|
||||
* Zxing license - {https://github.com/zxing/zxing/blob/master/LICENSE}
|
||||
*/
|
||||
public class RotateLuminanceSource extends LuminanceSource {
|
||||
|
||||
private static final int THUMBNAIL_SCALE_FACTOR = 2;
|
||||
|
||||
private final byte[] yuvData;
|
||||
private final int dataWidth;
|
||||
private final int dataHeight;
|
||||
private final int left;
|
||||
private final int top;
|
||||
|
||||
RotateLuminanceSource(byte[] yuvData,
|
||||
int dataWidth,
|
||||
int dataHeight,
|
||||
int left,
|
||||
int top,
|
||||
int width,
|
||||
int height,
|
||||
boolean reverseHorizontal) {
|
||||
super(width, height);
|
||||
|
||||
if (left + width > dataWidth || top + height > dataHeight) {
|
||||
throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
|
||||
}
|
||||
|
||||
this.yuvData = yuvData;
|
||||
this.dataWidth = dataWidth;
|
||||
this.dataHeight = dataHeight;
|
||||
this.left = left;
|
||||
this.top = top;
|
||||
if (reverseHorizontal) {
|
||||
reverseHorizontal(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRow(int y, byte[] row) {
|
||||
if (y < 0 || y >= getHeight()) {
|
||||
throw new IllegalArgumentException("Requested row is outside the image: " + y);
|
||||
}
|
||||
int width = getWidth();
|
||||
if (row == null || row.length < width) {
|
||||
row = new byte[width];
|
||||
}
|
||||
int offset = (y + top) * dataWidth + left;
|
||||
System.arraycopy(yuvData, offset, row, 0, width);
|
||||
return row;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getMatrix() {
|
||||
int width = getWidth();
|
||||
int height = getHeight();
|
||||
|
||||
// If the caller asks for the entire underlying image, save the copy and give them the
|
||||
// original data. The docs specifically warn that result.length must be ignored.
|
||||
if (width == dataWidth && height == dataHeight) {
|
||||
return yuvData;
|
||||
}
|
||||
|
||||
int area = width * height;
|
||||
byte[] matrix = new byte[area];
|
||||
int inputOffset = top * dataWidth + left;
|
||||
|
||||
// If the width matches the full width of the underlying data, perform a single copy.
|
||||
if (width == dataWidth) {
|
||||
System.arraycopy(yuvData, inputOffset, matrix, 0, area);
|
||||
return matrix;
|
||||
}
|
||||
|
||||
// Otherwise copy one cropped row at a time.
|
||||
for (int y = 0; y < height; y++) {
|
||||
int outputOffset = y * width;
|
||||
System.arraycopy(yuvData, inputOffset, matrix, outputOffset, width);
|
||||
inputOffset += dataWidth;
|
||||
}
|
||||
return matrix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCropSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LuminanceSource crop(int left, int top, int width, int height) {
|
||||
return new RotateLuminanceSource(yuvData,
|
||||
dataWidth,
|
||||
dataHeight,
|
||||
this.left + left,
|
||||
this.top + top,
|
||||
width,
|
||||
height,
|
||||
false);
|
||||
}
|
||||
|
||||
public int[] renderThumbnail() {
|
||||
int width = getWidth() / THUMBNAIL_SCALE_FACTOR;
|
||||
int height = getHeight() / THUMBNAIL_SCALE_FACTOR;
|
||||
int[] pixels = new int[width * height];
|
||||
byte[] yuv = yuvData;
|
||||
int inputOffset = top * dataWidth + left;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
int outputOffset = y * width;
|
||||
for (int x = 0; x < width; x++) {
|
||||
int grey = yuv[inputOffset + x * THUMBNAIL_SCALE_FACTOR] & 0xff;
|
||||
pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101);
|
||||
}
|
||||
inputOffset += dataWidth * THUMBNAIL_SCALE_FACTOR;
|
||||
}
|
||||
return pixels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return width of image from {@link #renderThumbnail()}
|
||||
*/
|
||||
public int getThumbnailWidth() {
|
||||
return getWidth() / THUMBNAIL_SCALE_FACTOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return height of image from {@link #renderThumbnail()}
|
||||
*/
|
||||
public int getThumbnailHeight() {
|
||||
return getHeight() / THUMBNAIL_SCALE_FACTOR;
|
||||
}
|
||||
|
||||
private void reverseHorizontal(int width, int height) {
|
||||
byte[] yuvData = this.yuvData;
|
||||
for (int y = 0, rowStart = top * dataWidth + left; y < height; y++, rowStart += dataWidth) {
|
||||
int middle = rowStart + width / 2;
|
||||
for (int x1 = rowStart, x2 = rowStart + width - 1; x1 < middle; x1++, x2--) {
|
||||
byte temp = yuvData[x1];
|
||||
yuvData[x1] = yuvData[x2];
|
||||
yuvData[x2] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRotateSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LuminanceSource rotateCounterClockwise() {
|
||||
byte[] rotatedData = new byte[yuvData.length];
|
||||
for (int y = 0; y < dataHeight; y++) {
|
||||
for (int x = 0; x < dataWidth; x++)
|
||||
rotatedData[x * dataHeight + dataHeight - y - 1] = yuvData[x + y * dataWidth];
|
||||
}
|
||||
return new RotateLuminanceSource(rotatedData, dataHeight, dataWidth, top, (dataWidth - left - getWidth()), getHeight(), getWidth(), false);
|
||||
}
|
||||
}
|
||||
@ -1,40 +0,0 @@
|
||||
package com.rncamerakit.camera.commands;
|
||||
|
||||
import android.content.Context;
|
||||
import android.hardware.Camera;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
|
||||
import com.rncamerakit.camera.CameraViewManager;
|
||||
import com.rncamerakit.SaveImageTask;
|
||||
import com.rncamerakit.camera.commands.Command;
|
||||
|
||||
public class Capture implements Command {
|
||||
|
||||
private final Context context;
|
||||
private boolean saveToCameraRoll;
|
||||
|
||||
public Capture(Context context, boolean saveToCameraRoll) {
|
||||
this.context = context;
|
||||
this.saveToCameraRoll = saveToCameraRoll;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final Promise promise) {
|
||||
try {
|
||||
tryTakePicture(promise);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void tryTakePicture(final Promise promise) throws Exception {
|
||||
CameraViewManager.getCamera().takePicture(null, null, new Camera.PictureCallback() {
|
||||
@Override
|
||||
public void onPictureTaken(byte[] data, Camera camera) {
|
||||
camera.stopPreview();
|
||||
new SaveImageTask(context, promise, saveToCameraRoll).execute(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
package com.rncamerakit.camera.commands;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
|
||||
public interface Command {
|
||||
void execute(Promise promise);
|
||||
}
|
||||
@ -1,69 +0,0 @@
|
||||
package com.rncamerakit.camera.permission;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.PermissionChecker;
|
||||
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.rncamerakit.SharedPrefs;
|
||||
|
||||
public class CameraPermission {
|
||||
private static final int CAMERA_PERMISSION_REQUEST_CODE = 1002;
|
||||
private static final int PERMISSION_GRANTED = 1;
|
||||
private static final int PERMISSION_NOT_DETERMINED = -1;
|
||||
private static final int PERMISSION_DENIED = 0;
|
||||
|
||||
private Promise requestAccessPromise;
|
||||
|
||||
public void requestAccess(Activity activity, Promise promise) {
|
||||
if (isPermissionGranted(activity)) {
|
||||
promise.resolve(true);
|
||||
}
|
||||
requestAccessPromise = promise;
|
||||
permissionRequested(activity, Manifest.permission.CAMERA);
|
||||
ActivityCompat.requestPermissions(activity,
|
||||
new String[]{Manifest.permission.CAMERA},
|
||||
CAMERA_PERMISSION_REQUEST_CODE);
|
||||
}
|
||||
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
if (isCameraPermission(requestCode, permissions)) {
|
||||
if (requestAccessPromise != null) {
|
||||
requestAccessPromise.resolve(grantResults[0] == PermissionChecker.PERMISSION_GRANTED);
|
||||
requestAccessPromise = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCameraPermission(int requestCode, String[] permissions) {
|
||||
if (permissions.length > 0) {
|
||||
return requestCode == CAMERA_PERMISSION_REQUEST_CODE &&
|
||||
Manifest.permission.CAMERA.equals(permissions[0]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int checkAuthorizationStatus(Activity activity) {
|
||||
final int statusCode = PermissionChecker.checkCallingOrSelfPermission(activity, Manifest.permission.CAMERA);
|
||||
if (statusCode == PermissionChecker.PERMISSION_GRANTED) {
|
||||
return PERMISSION_GRANTED;
|
||||
}
|
||||
if (requestingPermissionForFirstTime(activity, Manifest.permission.CAMERA)) {
|
||||
return PERMISSION_NOT_DETERMINED;
|
||||
}
|
||||
return PERMISSION_DENIED;
|
||||
}
|
||||
|
||||
private boolean requestingPermissionForFirstTime(Activity activity, String permissionName) {
|
||||
return !SharedPrefs.getBoolean(activity, permissionName);
|
||||
}
|
||||
|
||||
private void permissionRequested(Activity activity, String permissionName) {
|
||||
SharedPrefs.putBoolean(activity, permissionName, true);
|
||||
}
|
||||
|
||||
private boolean isPermissionGranted(Activity activity) {
|
||||
return checkAuthorizationStatus(activity) == PermissionChecker.PERMISSION_GRANTED;
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
package com.rncamerakit.camera.permission;
|
||||
|
||||
import com.rncamerakit.camera.CameraModule;
|
||||
|
||||
public class CameraPermissionRequestCallback {
|
||||
|
||||
private CameraModule cameraModule;
|
||||
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
cameraModule.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
public void setCameraModule(CameraModule cameraModule) {
|
||||
this.cameraModule = cameraModule;
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-feature android:name="android.hardware.camera.any" />
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.4.10'
|
||||
ext {
|
||||
buildToolsVersion = "28.0.3"
|
||||
minSdkVersion = 23
|
||||
@ -13,6 +14,7 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
classpath('com.android.tools.build:gradle:4.1.1')
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#Thu Nov 12 13:47:49 PST 2020
|
||||
#Thu Nov 12 14:10:32 PST 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
|
||||
|
||||
@ -4,20 +4,20 @@ import {
|
||||
Text,
|
||||
View,
|
||||
TouchableOpacity,
|
||||
Alert
|
||||
Image,
|
||||
} from 'react-native';
|
||||
|
||||
import { CameraKitCamera } from '../../src';
|
||||
import CameraScreenExample from './CameraScreenExample';
|
||||
import BarcodeScreenExample from './BarcodeScreenExample';
|
||||
import CameraExample from './CameraExample';
|
||||
|
||||
import CameraScreen from './CameraScreen';
|
||||
import BarcodeScreen from './BarcodeScreen';
|
||||
|
||||
export default class App extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
example: undefined
|
||||
example: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@ -34,65 +34,58 @@ export default class App extends Component {
|
||||
React Native Camera Kit
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
|
||||
<View style={styles.container}>
|
||||
<TouchableOpacity onPress={() => this.setState({ example: BarcodeScreen })}>
|
||||
<TouchableOpacity style={styles.button} onPress={() => this.setState({ example: CameraExample })}>
|
||||
<Text style={styles.buttonText}>
|
||||
Barcode scanner Screen
|
||||
Camera
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity onPress={() => this.setState({ example: CameraScreen })}>
|
||||
<TouchableOpacity style={styles.button} onPress={() => this.setState({ example: CameraScreenExample })}>
|
||||
<Text style={styles.buttonText}>
|
||||
Camera Screen
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity onPress={() => this.onCheckCameraAuthoPressed()}>
|
||||
<TouchableOpacity style={styles.button} onPress={() => this.setState({ example: BarcodeScreenExample })}>
|
||||
<Text style={styles.buttonText}>
|
||||
Camera Permission Status
|
||||
Barcode Scanner
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
</View>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
async onCheckCameraAuthoPressed() {
|
||||
const success = await CameraKitCamera.checkDeviceCameraAuthorizationStatus();
|
||||
if (success) {
|
||||
Alert.alert('You have permission 🤗')
|
||||
}
|
||||
else {
|
||||
Alert.alert('No permission 😳')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingTop: 60,
|
||||
paddingTop: 30,
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5FCFF',
|
||||
marginHorizontal: 24,
|
||||
},
|
||||
headerContainer: {
|
||||
flexDirection: 'column',
|
||||
backgroundColor: '#F5FCFF',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingTop: 100
|
||||
paddingTop: 100,
|
||||
},
|
||||
headerText: {
|
||||
color: 'black',
|
||||
fontSize: 24
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
button: {
|
||||
height: 60,
|
||||
borderRadius: 30,
|
||||
marginVertical: 12,
|
||||
width: '100%',
|
||||
backgroundColor: '#dddddd',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
buttonText: {
|
||||
color: 'blue',
|
||||
marginBottom: 20,
|
||||
fontSize: 20
|
||||
}
|
||||
textAlign: 'center',
|
||||
fontSize: 20,
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Alert } from 'react-native';
|
||||
import CameraKitCameraScreen from '../../src/CameraScreen/CameraKitCameraScreen';
|
||||
import CameraScreen from '../../src/CameraScreen';
|
||||
import CheckingScreen from './CheckingScreen';
|
||||
|
||||
export default class CameraScreen extends Component {
|
||||
export default class BarcodeScreenExample extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@ -27,7 +27,7 @@ export default class CameraScreen extends Component {
|
||||
return <CameraScreen />;
|
||||
}
|
||||
return (
|
||||
<CameraKitCameraScreen
|
||||
<CameraScreen
|
||||
actions={{ rightButtonText: 'Done', leftButtonText: 'Cancel' }}
|
||||
onBottomButtonPressed={(event) => this.onBottomButtonPressed(event)}
|
||||
flashImages={{
|
||||
44
example/src/CameraExample.tsx
Normal file
44
example/src/CameraExample.tsx
Normal file
@ -0,0 +1,44 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Alert, View, Text, SafeAreaView, StyleSheet } from 'react-native';
|
||||
import Camera from '../../src/Camera';
|
||||
import { CameraType } from '../../src/CameraScreen/CameraScreenBase';
|
||||
|
||||
export default class CameraExample extends Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.cameraContainer}>
|
||||
<Camera
|
||||
ref={(ref) => (this.camera = ref)}
|
||||
type={CameraType.Back} // optional
|
||||
style={{ flex: 1 }}
|
||||
flashMode="auto"
|
||||
cameraOptions={{
|
||||
flashMode: 'auto', // on/off/auto(default)
|
||||
focusMode: 'on', // off/on(default)
|
||||
zoomMode: 'on', // off/on(default)
|
||||
ratioOverlay: '1:1', // optional
|
||||
ratioOverlayColor: '#00000077', // optional
|
||||
}}
|
||||
resetFocusTimeout={0}
|
||||
resetFocusWhenMotionDetected={false}
|
||||
saveToCameraRole={false} // iOS only
|
||||
scanBarcode={false} // optional
|
||||
showFrame={false} // Barcode only, optional
|
||||
laserColor="red" // Barcode only, optional
|
||||
frameColor="yellow" // Barcode only, optional
|
||||
surfaceColor="blue" // Barcode only, optional
|
||||
onReadCode={(event) => console.log(event.nativeEvent.codeStringValue)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create(
|
||||
{
|
||||
cameraContainer: {
|
||||
flex: 1,
|
||||
backgroundColor: 'black',
|
||||
},
|
||||
},
|
||||
);
|
||||
@ -1,8 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Alert } from 'react-native';
|
||||
import CameraKitCameraScreen from '../../src/CameraScreen/CameraKitCameraScreen';
|
||||
import CameraScreen from '../../src/CameraScreen';
|
||||
import { CameraOptions } from '../../src/CameraScreen/CameraScreenBase';
|
||||
|
||||
export default class CameraScreen extends Component {
|
||||
export default class CameraScreenExample extends Component {
|
||||
onBottomButtonPressed(event) {
|
||||
const captureImages = JSON.stringify(event.captureImages);
|
||||
Alert.alert(
|
||||
@ -14,17 +15,19 @@ export default class CameraScreen extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const options: CameraOptions = { flashMode: 'auto', focusMode: 'on', zoomMode: 'on' };
|
||||
return (
|
||||
<CameraKitCameraScreen
|
||||
<CameraScreen
|
||||
actions={{ rightButtonText: 'Done', leftButtonText: 'Cancel' }}
|
||||
onBottomButtonPressed={(event) => this.onBottomButtonPressed(event)}
|
||||
cameraOptions={options}
|
||||
flashImages={{
|
||||
on: require('../images/flashOn.png'),
|
||||
off: require('./../images/flashOff.png'),
|
||||
auto: require('./../images/flashAuto.png'),
|
||||
off: require('../images/flashOff.png'),
|
||||
auto: require('../images/flashAuto.png'),
|
||||
}}
|
||||
cameraFlipImage={require('./../images/cameraFlipIcon.png')}
|
||||
captureButtonImage={require('./../images/cameraButton.png')}
|
||||
cameraFlipImage={require('../images/cameraFlipIcon.png')}
|
||||
captureButtonImage={require('../images/cameraButton.png')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
View,
|
||||
TouchableOpacity,
|
||||
Text,
|
||||
StyleSheet
|
||||
} from 'react-native';
|
||||
import BarcodeScreen from './BarcodeScreen';
|
||||
|
||||
export default class ExampleScreen extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
example: undefined
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.example) {
|
||||
const ExampleScreen = this.state.example;
|
||||
return <ExampleScreen />;
|
||||
}
|
||||
return (
|
||||
<View style = {styles.container}>
|
||||
<TouchableOpacity onPress={(() => this.setState({example : BarcodeScreen}))}>
|
||||
<Text style={styles.buttonText}>
|
||||
Back button
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
// justifyContent: 'center',
|
||||
paddingTop: 60,
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5FCFF',
|
||||
},
|
||||
buttonText: {
|
||||
color: 'blue',
|
||||
marginBottom: 20,
|
||||
fontSize: 20
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
40
example/src/CheckingScreen.tsx
Normal file
40
example/src/CheckingScreen.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import React, { Component } from 'react';
|
||||
import { View, TouchableOpacity, Text, StyleSheet } from 'react-native';
|
||||
import BarcodeScreen from './BarcodeScreenExample';
|
||||
|
||||
export default class CheckingScreen extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
example: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.example) {
|
||||
const CheckingScreen = this.state.example;
|
||||
return <CheckingScreen />;
|
||||
}
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<TouchableOpacity onPress={() => this.setState({ example: BarcodeScreen })}>
|
||||
<Text style={styles.buttonText}>Back button</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingTop: 60,
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#F5FCFF',
|
||||
},
|
||||
buttonText: {
|
||||
color: 'blue',
|
||||
marginBottom: 20,
|
||||
fontSize: 20,
|
||||
},
|
||||
});
|
||||
@ -27,6 +27,7 @@
|
||||
"devDependencies": {
|
||||
"@react-native-community/eslint-config": "^2.0.0",
|
||||
"@types/jest": "^26.0.0",
|
||||
"@types/lodash": "^4.14.165",
|
||||
"@types/react": "^16.9.19",
|
||||
"@types/react-native": "0.62.13",
|
||||
"babel-jest": "22.4.1",
|
||||
@ -38,7 +39,8 @@
|
||||
"prettier": "^2.1.2",
|
||||
"react": "16.11.0",
|
||||
"react-native": "0.62.2",
|
||||
"react-test-renderer": "16.6.0"
|
||||
"react-test-renderer": "16.6.0",
|
||||
"typescript": "^4.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
|
||||
35
src/Camera.android.tsx
Normal file
35
src/Camera.android.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { requireNativeComponent, findNodeHandle, NativeModules, processColor } from 'react-native';
|
||||
|
||||
const { RNCameraKitModule } = NativeModules;
|
||||
const NativeCamera = requireNativeComponent('CKCameraManager');
|
||||
|
||||
function Camera(props, ref) {
|
||||
const nativeRef = React.useRef();
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
capture: async (options = {}) => {
|
||||
// Because RN doesn't support return types for ViewManager methods
|
||||
// we must use the general module and tell it what View it's supposed to be using
|
||||
return await RNCameraKitModule.capture(options, findNodeHandle(nativeRef.current));
|
||||
},
|
||||
requestDeviceCameraAuthorization: async () => {
|
||||
return await RNCameraKitModule.requestDeviceCameraAuthorization();
|
||||
},
|
||||
}));
|
||||
|
||||
const transformedProps = _.cloneDeep(props);
|
||||
_.update(transformedProps, 'cameraOptions.ratioOverlayColor', (c) => processColor(c));
|
||||
_.update(transformedProps, 'frameColor', (c) => processColor(c));
|
||||
_.update(transformedProps, 'laserColor', (c) => processColor(c));
|
||||
_.update(transformedProps, 'surfaceColor', (c) => processColor(c));
|
||||
|
||||
return <NativeCamera flashMode={props.flashMode} ref={nativeRef} {...props} />;
|
||||
}
|
||||
|
||||
const { PORTRAIT, PORTRAIT_UPSIDE_DOWN, LANDSCAPE_LEFT, LANDSCAPE_RIGHT } = RNCameraKitModule.getConstants();
|
||||
|
||||
export { PORTRAIT, PORTRAIT_UPSIDE_DOWN, LANDSCAPE_LEFT, LANDSCAPE_RIGHT };
|
||||
|
||||
export default React.forwardRef(Camera);
|
||||
52
src/Camera.ios.tsx
Normal file
52
src/Camera.ios.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import * as _ from 'lodash';
|
||||
import React, { useEffect } from 'react';
|
||||
import { requireNativeComponent, NativeModules, processColor } from 'react-native';
|
||||
|
||||
const { CKCameraManager } = NativeModules;
|
||||
const NativeCamera = requireNativeComponent('CKCamera');
|
||||
|
||||
function Camera(props, ref) {
|
||||
const nativeRef = React.useRef();
|
||||
|
||||
useEffect(() => {
|
||||
CKCameraManager.changeCamera();
|
||||
}, [props.type]);
|
||||
|
||||
useEffect(() => {
|
||||
CKCameraManager.setFlashMode(props.flashMode);
|
||||
}, [props.flashMode]);
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
capture: async () => {
|
||||
return await CKCameraManager.capture({});
|
||||
},
|
||||
requestDeviceCameraAuthorization: async () => {
|
||||
return await CKCameraManager.checkDeviceCameraAuthorizationStatus();
|
||||
},
|
||||
checkDeviceCameraAuthorizationStatus: async () => {
|
||||
return await CKCameraManager.checkDeviceCameraAuthorizationStatus();
|
||||
},
|
||||
changeCamera: async () => {
|
||||
return await CKCameraManager.changeCamera();
|
||||
},
|
||||
setFlashMode: async (flashMode = 'auto') => {
|
||||
return await CKCameraManager.setFlashMode(flashMode);
|
||||
},
|
||||
setTorchMode: async (torchMode = '') => {
|
||||
return await CKCameraManager.setTorchMode(torchMode);
|
||||
},
|
||||
}));
|
||||
|
||||
const transformedProps = _.cloneDeep(props);
|
||||
_.update(transformedProps, 'cameraOptions.ratioOverlayColor', (c) => processColor(c));
|
||||
|
||||
return <NativeCamera ref={nativeRef} {...transformedProps} />;
|
||||
}
|
||||
|
||||
Camera.defaultProps = {
|
||||
resetFocusTimeout: 0,
|
||||
resetFocusWhenMotionDetected: true,
|
||||
saveToCameraRoll: true,
|
||||
};
|
||||
|
||||
export default React.forwardRef(Camera);
|
||||
@ -1,61 +0,0 @@
|
||||
import * as _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import {
|
||||
requireNativeComponent,
|
||||
NativeModules,
|
||||
processColor,
|
||||
} from 'react-native';
|
||||
|
||||
const NativeCamera = requireNativeComponent('CameraView', null);
|
||||
const NativeCameraModule = NativeModules.RNKitCameraModule;
|
||||
const TORCH_MODE_ON = 'on';
|
||||
const TORCH_MODE_CALL_ARG = 'torch';
|
||||
|
||||
export default class CameraKitCamera extends React.Component {
|
||||
async logData() {
|
||||
console.log('front Camera?', await NativeCameraModule.hasFrontCamera());
|
||||
console.log('hasFlash?', await NativeCameraModule.hasFlashForCurrentCamera());
|
||||
console.log('flashMode?', await NativeCameraModule.getFlashMode());
|
||||
}
|
||||
|
||||
static async requestDeviceCameraAuthorization() {
|
||||
return await NativeCameraModule.requestDeviceCameraAuthorization();
|
||||
}
|
||||
|
||||
async capture(saveToCameraRoll = true) {
|
||||
return await NativeCameraModule.capture(saveToCameraRoll);
|
||||
}
|
||||
|
||||
async changeCamera() {
|
||||
return await NativeCameraModule.changeCamera();
|
||||
}
|
||||
|
||||
async setTorchMode(torchMode) {
|
||||
if (torchMode == TORCH_MODE_ON){
|
||||
return await NativeCameraModule.setFlashMode(TORCH_MODE_CALL_ARG);
|
||||
}
|
||||
return await NativeCameraModule.setFlashMode(torchMode);
|
||||
}
|
||||
|
||||
async setFlashMode(flashMode = 'auto') {
|
||||
return await NativeCameraModule.setFlashMode(flashMode);
|
||||
}
|
||||
|
||||
static async checkDeviceCameraAuthorizationStatus() {
|
||||
return await NativeCameraModule.checkDeviceCameraAuthorizationStatus();
|
||||
}
|
||||
|
||||
static async hasCameraPermission() {
|
||||
return await NativeCameraModule.hasCameraPermission();
|
||||
}
|
||||
|
||||
render() {
|
||||
const transformedProps = _.cloneDeep(this.props);
|
||||
_.update(transformedProps, 'cameraOptions.ratioOverlayColor', (c) => processColor(c));
|
||||
_.update(transformedProps, 'frameColor', (c) => processColor(c));
|
||||
_.update(transformedProps, 'laserColor', (c) => processColor(c));
|
||||
_.update(transformedProps, 'surfaceColor', (c) => processColor(c));
|
||||
|
||||
return <NativeCamera {...transformedProps}/>;
|
||||
}
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
import * as _ from 'lodash';
|
||||
import React from 'react';
|
||||
import {
|
||||
requireNativeComponent,
|
||||
NativeModules,
|
||||
processColor,
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
|
||||
const NativeCamera = requireNativeComponent('CKCamera', null);
|
||||
const NativeCameraAction = NativeModules.CKCameraManager;
|
||||
|
||||
export default class CameraKitCamera extends React.Component {
|
||||
static async checkDeviceCameraAuthorizationStatus() {
|
||||
return await NativeCameraAction.checkDeviceCameraAuthorizationStatus();
|
||||
}
|
||||
|
||||
static async requestDeviceCameraAuthorization() {
|
||||
return await NativeCameraAction.requestDeviceCameraAuthorization();
|
||||
}
|
||||
|
||||
capture(options) {
|
||||
if (Platform.OS === 'ios') {
|
||||
return NativeCameraAction.capture(options);
|
||||
}
|
||||
if (Platform.OS === 'android') {
|
||||
// Android has not been updated to use props yet
|
||||
return NativeCameraAction.capture(!!this.props.saveToCameraRoll);
|
||||
}
|
||||
}
|
||||
|
||||
async changeCamera() {
|
||||
return await NativeCameraAction.changeCamera();
|
||||
}
|
||||
|
||||
async setFlashMode(flashMode = 'auto') {
|
||||
return await NativeCameraAction.setFlashMode(flashMode);
|
||||
}
|
||||
|
||||
async setTorchMode(torchMode = '') {
|
||||
return await NativeCameraAction.setTorchMode(torchMode);
|
||||
}
|
||||
|
||||
render() {
|
||||
const transformedProps = _.cloneDeep(this.props);
|
||||
_.update(transformedProps, 'cameraOptions.ratioOverlayColor', (c) => processColor(c));
|
||||
return (
|
||||
<NativeCamera {...transformedProps} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CameraKitCamera.defaultProps = {
|
||||
resetFocusTimeout: 0,
|
||||
resetFocusWhenMotionDetected: true,
|
||||
saveToCameraRoll: true,
|
||||
};
|
||||
@ -1,63 +0,0 @@
|
||||
import {NativeModules} from 'react-native';
|
||||
const NativeGalleryModule = NativeModules.NativeGalleryModule;
|
||||
|
||||
async function getAlbumsWithThumbnails() {
|
||||
return await NativeGalleryModule.getAlbumsWithThumbnails();
|
||||
}
|
||||
|
||||
async function getImageUriForId(imageId) {
|
||||
// Return what getImagesForIds() typically returns in the 'uri' field.
|
||||
return `file://${imageId}`;
|
||||
}
|
||||
|
||||
async function getImagesForIds(imagesUris = []) {
|
||||
return await NativeGalleryModule.getImagesForUris(imagesUris);
|
||||
}
|
||||
|
||||
async function getImageForTapEvent(nativeEvent) {
|
||||
const selectedImageId = nativeEvent.selected;
|
||||
const imageUri = selectedImageId && await getImageUriForId(selectedImageId);
|
||||
return {selectedImageId, imageUri, width: nativeEvent.width, height: nativeEvent.height};
|
||||
}
|
||||
|
||||
async function getImagesForCameraEvent(event) {
|
||||
if (!event.captureImages) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const images = [];
|
||||
event.captureImages.forEach(async (image) => {
|
||||
images.push({
|
||||
...image,
|
||||
uri: await getImageUriForId(image.uri)
|
||||
});
|
||||
});
|
||||
return images;
|
||||
}
|
||||
|
||||
async function checkDevicePhotosAuthorizationStatus() {
|
||||
return await NativeGalleryModule.checkDeviceStorageAuthorizationStatus();
|
||||
}
|
||||
|
||||
async function requestDevicePhotosAuthorization() {
|
||||
return await NativeGalleryModule.requestDeviceStorageAuthorization();
|
||||
}
|
||||
|
||||
async function resizeImage(image = {}, quality = 'original') {
|
||||
if (quality === 'original') {
|
||||
return images;
|
||||
}
|
||||
return await NativeGalleryModule.resizeImage(image, quality);
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
checkDevicePhotosAuthorizationStatus,
|
||||
requestDevicePhotosAuthorization,
|
||||
getAlbumsWithThumbnails,
|
||||
getImageUriForId,
|
||||
getImagesForIds,
|
||||
getImageForTapEvent,
|
||||
getImagesForCameraEvent,
|
||||
resizeImage
|
||||
}
|
||||
@ -1,69 +0,0 @@
|
||||
import {
|
||||
NativeModules,
|
||||
} from 'react-native';
|
||||
|
||||
var CKGallery = NativeModules.CKGalleryManager;
|
||||
|
||||
async function getAlbumsWithThumbnails() {
|
||||
return await CKGallery.getAlbumsWithThumbnails();
|
||||
}
|
||||
|
||||
async function getImageUriForId(imageId, imageQuality) {
|
||||
const {images} = await CKGallery.getImagesForIds([imageId], imageQuality);
|
||||
if (!images) {
|
||||
return;
|
||||
}
|
||||
if (images.length === 1) {
|
||||
return images[0].uri;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
async function getImagesForIds(imagesId = [], imageQuality) {
|
||||
return await CKGallery.getImagesForIds(imagesId, imageQuality);
|
||||
}
|
||||
|
||||
async function getImageForTapEvent(nativeEvent) {
|
||||
let selectedImageId;
|
||||
let imageUri;
|
||||
if (nativeEvent.selectedId) {
|
||||
selectedImageId = nativeEvent.selectedId;
|
||||
imageUri = nativeEvent.selected;
|
||||
} else {
|
||||
selectedImageId = nativeEvent.selected;
|
||||
imageUri = await getImageUriForId(selectedImageId);
|
||||
}
|
||||
return {selectedImageId, imageUri, width: nativeEvent.width, height: nativeEvent.height};
|
||||
}
|
||||
|
||||
async function getImagesForCameraEvent(event) {
|
||||
return event.captureImages || [];
|
||||
}
|
||||
|
||||
async function resizeImage(image = {}, quality = 'original') {
|
||||
if (quality === 'original') {
|
||||
return images;
|
||||
}
|
||||
|
||||
return await CKGallery.resizeImage(image, quality);
|
||||
}
|
||||
|
||||
async function checkDevicePhotosAuthorizationStatus() {
|
||||
return await CKGallery.checkDevicePhotosAuthorizationStatus();
|
||||
}
|
||||
|
||||
async function requestDevicePhotosAuthorization() {
|
||||
return await CKGallery.requestDevicePhotosAuthorization();
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
getAlbumsWithThumbnails,
|
||||
getImageUriForId,
|
||||
getImagesForIds,
|
||||
getImageForTapEvent,
|
||||
getImagesForCameraEvent,
|
||||
checkDevicePhotosAuthorizationStatus,
|
||||
requestDevicePhotosAuthorization,
|
||||
resizeImage
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import React, {Component} from 'react';
|
||||
import ReactNative, {
|
||||
requireNativeComponent,
|
||||
UIManager,
|
||||
processColor
|
||||
} from 'react-native';
|
||||
|
||||
const resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource');
|
||||
const GalleryView = requireNativeComponent('GalleryView', null);
|
||||
const ALL_PHOTOS = 'All Photos';
|
||||
const COMMAND_REFRESH_GALLERY = 1;
|
||||
|
||||
export default class CameraKitGalleryView extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onTapImage = this.onTapImage.bind(this);
|
||||
}
|
||||
|
||||
async refreshGalleryView(lastEditedImage = '') {
|
||||
UIManager.dispatchViewManagerCommand(
|
||||
ReactNative.findNodeHandle(this),
|
||||
COMMAND_REFRESH_GALLERY,
|
||||
[lastEditedImage]
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
modifyGalleryViewContentOffset (offset) {
|
||||
//do nothing. compatability with ios
|
||||
}
|
||||
|
||||
render() {
|
||||
const transformedProps = _.cloneDeep(this.props);
|
||||
transformedProps.albumName = this.props.albumName ? this.props.albumName : ALL_PHOTOS;
|
||||
if (transformedProps.fileTypeSupport && transformedProps.fileTypeSupport.unsupportedImage) {
|
||||
_.update(transformedProps, 'fileTypeSupport.unsupportedImage', (image) => resolveAssetSource(image).uri);
|
||||
}
|
||||
|
||||
if (_.get(transformedProps, 'customButtonStyle.image')) {
|
||||
_.update(transformedProps, 'customButtonStyle.image', (image) => resolveAssetSource(image).uri);
|
||||
}
|
||||
|
||||
const customButtonBkgColor = _.get(transformedProps, 'customButtonStyle.backgroundColor');
|
||||
if (customButtonBkgColor) {
|
||||
_.update(transformedProps, 'customButtonStyle.backgroundColor', (color) => processColor(customButtonBkgColor));
|
||||
}
|
||||
|
||||
const selectedImageDeprecated = transformedProps.selectedImageIcon;
|
||||
if (selectedImageDeprecated && _.isNumber(selectedImageDeprecated)) {
|
||||
transformedProps.selectedImageIcon = resolveAssetSource(selectedImageDeprecated).uri;
|
||||
}
|
||||
|
||||
const unselectedImageDeprecated = transformedProps.unSelectedImageIcon;
|
||||
if (unselectedImageDeprecated && _.isNumber(unselectedImageDeprecated)) {
|
||||
transformedProps.unSelectedImageIcon = resolveAssetSource(unselectedImageDeprecated).uri;
|
||||
}
|
||||
|
||||
const selectedImage = _.get(transformedProps, 'selection.selectedImage');
|
||||
if (selectedImage && _.isNumber(selectedImage)) {
|
||||
_.update(transformedProps, 'selection.selectedImage', (image) => resolveAssetSource(image).uri);
|
||||
}
|
||||
|
||||
const unselectedImage = _.get(transformedProps, 'selection.unselectedImage');
|
||||
if (unselectedImage && _.isNumber(unselectedImage)) {
|
||||
_.update(transformedProps, 'selection.unselectedImage', (image) => resolveAssetSource(image).uri);
|
||||
}
|
||||
|
||||
const selectionPosition = _.get(transformedProps, 'selection.imagePosition');
|
||||
if (selectionPosition) {
|
||||
const positionCode = this.transformSelectedImagePosition(selectionPosition);
|
||||
_.update(transformedProps, 'selection.imagePosition', (position) => positionCode);
|
||||
}
|
||||
|
||||
const selectionOverlayColor = _.get(transformedProps, 'selection.overlayColor');
|
||||
if (selectionOverlayColor) {
|
||||
_.update(transformedProps, 'selection.overlayColor', (color) => processColor(selectionOverlayColor));
|
||||
}
|
||||
|
||||
return <GalleryView {...transformedProps} onTapImage={this.onTapImage}/>
|
||||
}
|
||||
|
||||
onTapImage(event) {
|
||||
if(this.props.onTapImage) {
|
||||
this.props.onTapImage(event);
|
||||
}
|
||||
}
|
||||
|
||||
transformSelectedImagePosition(position) {
|
||||
switch (position) {
|
||||
case 'top-right': return 0;
|
||||
case 'top-left': return 1;
|
||||
case 'bottom-right': return 2;
|
||||
case 'bottom-left': return 3;
|
||||
case 'center': return 4;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import React, {Component} from 'react';
|
||||
|
||||
import {
|
||||
requireNativeComponent,
|
||||
NativeModules,
|
||||
processColor
|
||||
} from 'react-native';
|
||||
|
||||
const GalleryView = requireNativeComponent('CKGalleryView', null);
|
||||
const GalleryViewManager = NativeModules.CKGalleryViewManager;
|
||||
const ALL_PHOTOS = 'All Photos';
|
||||
const DEFAULT_COLUMN_COUNT = 3;
|
||||
const resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource');
|
||||
|
||||
export default class CameraKitGalleryView extends Component {
|
||||
render() {
|
||||
const transformedProps = _.cloneDeep(this.props);
|
||||
transformedProps.albumName = this.props.albumName ? this.props.albumName : ALL_PHOTOS;
|
||||
transformedProps.columnCount = this.props.columnCount && this.props.columnCount > 0 ? this.props.columnCount : DEFAULT_COLUMN_COUNT;
|
||||
_.update(transformedProps, 'fileTypeSupport.unsupportedOverlayColor', (c) => processColor(c));
|
||||
_.update(transformedProps, 'fileTypeSupport.unsupportedTextColor', (c) => processColor(c));
|
||||
if (transformedProps.fileTypeSupport && transformedProps.fileTypeSupport.unsupportedImage) {
|
||||
_.update(transformedProps, 'fileTypeSupport.unsupportedImage', (image) => resolveAssetSource(image));
|
||||
}
|
||||
if (_.get(transformedProps, 'customButtonStyle.image')) {
|
||||
_.update(transformedProps, 'customButtonStyle.image', (image) => resolveAssetSource(image));
|
||||
}
|
||||
|
||||
if (_.get(transformedProps, 'customButtonStyle.backgroundColor')) {
|
||||
_.update(transformedProps, 'customButtonStyle.backgroundColor', (c) => processColor(c));
|
||||
}
|
||||
|
||||
if (_.get(transformedProps, 'selection.selectedImage')) {
|
||||
_.update(transformedProps, 'selection.selectedImage', (image) => resolveAssetSource(image));
|
||||
}
|
||||
|
||||
if (_.get(transformedProps, 'selection.unselectedImage')) {
|
||||
_.update(transformedProps, 'selection.unselectedImage', (image) => resolveAssetSource(image));
|
||||
}
|
||||
|
||||
if (_.get(transformedProps, 'selection.overlayColor')) {
|
||||
_.update(transformedProps, 'selection.overlayColor', (c) => processColor(c));
|
||||
}
|
||||
|
||||
return <GalleryView {...transformedProps}/>
|
||||
}
|
||||
|
||||
async getSelectedImages() {
|
||||
return await GalleryViewManager.getSelectedImages();
|
||||
}
|
||||
|
||||
async refreshGalleryView(selectedImages = []) {
|
||||
return await GalleryViewManager.refreshGalleryView(selectedImages);
|
||||
}
|
||||
|
||||
modifyGalleryViewContentOffset (offset) {
|
||||
GalleryViewManager.modifyGalleryViewContentOffset(offset);
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,10 @@
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import CameraScreenBase from './CameraKitCameraScreenBase';
|
||||
import { View } from 'react-native';
|
||||
import CameraScreenBase from './CameraScreenBase';
|
||||
|
||||
export default class CameraScreen extends CameraScreenBase {
|
||||
renderGap() {
|
||||
return (
|
||||
<View style={{flex: 10, flexDirection: 'column'}}/>
|
||||
);
|
||||
return <View style={{ flex: 10, flexDirection: 'column' }} />;
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -20,5 +18,3 @@ export default class CameraScreen extends CameraScreenBase {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import CameraScreenBase from './CameraKitCameraScreenBase';
|
||||
import { View } from 'react-native';
|
||||
import CameraScreenBase from './CameraScreenBase';
|
||||
|
||||
export default class CameraScreen extends CameraScreenBase {
|
||||
render() {
|
||||
@ -12,10 +12,9 @@ import {
|
||||
processColor,
|
||||
} from 'react-native';
|
||||
import _ from 'lodash';
|
||||
import CameraKitCamera from './../CameraKitCamera';
|
||||
import Camera from '../Camera';
|
||||
|
||||
const IsIOS = Platform.OS === 'ios';
|
||||
const GalleryManager = IsIOS ? NativeModules.CKGalleryManager : NativeModules.NativeGalleryModule;
|
||||
const GalleryManager = Platform.OS === 'ios' ? NativeModules.CKGalleryManager : NativeModules.NativeGalleryModule;
|
||||
|
||||
const FLASH_MODE_AUTO = 'auto';
|
||||
const FLASH_MODE_ON = 'on';
|
||||
@ -26,8 +25,53 @@ const OVERLAY_DEFAULT_COLOR = '#ffffff77';
|
||||
const OFFSET_FRAME = 30;
|
||||
const FRAME_HEIGHT = 200;
|
||||
|
||||
export default class CameraScreenBase extends Component {
|
||||
export enum CameraType {
|
||||
Front = 'front',
|
||||
Back = 'back'
|
||||
}
|
||||
|
||||
export type CameraOptions = {
|
||||
flashMode: 'on' | 'off' | 'auto'
|
||||
focusMode: 'on' | 'off'
|
||||
zoomMode: 'on'
|
||||
ratioOverlay?: string
|
||||
ratioOverlayColor?: string
|
||||
}
|
||||
|
||||
export type Props = {
|
||||
allowCaptureRetake: boolean,
|
||||
cameraRatioOverlay: any,
|
||||
cameraOptions: CameraOptions
|
||||
scannerOptions: any,
|
||||
offsetForScannerFrame: any,
|
||||
heightForScannerFrame: any,
|
||||
colorForScannerFrame: any,
|
||||
cameraFlipImage: any,
|
||||
hideControls: any,
|
||||
showFrame: any,
|
||||
captureButtonImage: any,
|
||||
scanBarcode: any,
|
||||
laserColor: any,
|
||||
frameColor: any,
|
||||
surfaceColor: any,
|
||||
onReadCode: () => void;
|
||||
onBottomButtonPressed: (any) => void;
|
||||
}
|
||||
|
||||
type State = {
|
||||
captureImages: any[],
|
||||
flashData: any,
|
||||
torchData: boolean,
|
||||
ratios: any[],
|
||||
cameraOptions: any,
|
||||
ratioArrayPosition: number,
|
||||
imageCaptured: any,
|
||||
captured: boolean,
|
||||
scannerOptions: any,
|
||||
type: CameraType,
|
||||
}
|
||||
|
||||
export default class CameraScreenBase extends Component<Props, State> {
|
||||
static propTypes = {
|
||||
allowCaptureRetake: PropTypes.bool,
|
||||
};
|
||||
@ -36,33 +80,41 @@ export default class CameraScreenBase extends Component {
|
||||
allowCaptureRetake: false,
|
||||
};
|
||||
|
||||
currentFlashArrayPosition: number;
|
||||
flashArray: any[];
|
||||
camera: any;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.currentFlashArrayPosition = 0;
|
||||
this.flashArray = [{
|
||||
mode: FLASH_MODE_AUTO,
|
||||
image: _.get(this.props, 'flashImages.auto'),
|
||||
},
|
||||
{
|
||||
mode: FLASH_MODE_ON,
|
||||
image: _.get(this.props, 'flashImages.on'),
|
||||
},
|
||||
{
|
||||
mode: FLASH_MODE_OFF,
|
||||
image: _.get(this.props, 'flashImages.off'),
|
||||
},
|
||||
this.flashArray = [
|
||||
{
|
||||
mode: FLASH_MODE_AUTO,
|
||||
image: _.get(this.props, 'flashImages.auto'),
|
||||
},
|
||||
{
|
||||
mode: FLASH_MODE_ON,
|
||||
image: _.get(this.props, 'flashImages.on'),
|
||||
},
|
||||
{
|
||||
mode: FLASH_MODE_OFF,
|
||||
image: _.get(this.props, 'flashImages.off'),
|
||||
},
|
||||
];
|
||||
|
||||
this.state = {
|
||||
captureImages: [],
|
||||
flashData: this.flashArray[this.currentFlashArrayPosition],
|
||||
torchData: false,
|
||||
ratios: [],
|
||||
cameraOptions: {},
|
||||
cameraOptions: this.props.cameraOptions,
|
||||
ratioArrayPosition: -1,
|
||||
imageCaptured: undefined,
|
||||
imageCaptured: false,
|
||||
captured: false,
|
||||
scannerOptions: {},
|
||||
type: CameraType.Back,
|
||||
};
|
||||
|
||||
this.onSetFlash = this.onSetFlash.bind(this);
|
||||
this.onSetTorch = this.onSetTorch.bind(this);
|
||||
this.onSwitchCameraPressed = this.onSwitchCameraPressed.bind(this);
|
||||
@ -78,8 +130,8 @@ export default class CameraScreenBase extends Component {
|
||||
this.setState({
|
||||
cameraOptions,
|
||||
scannerOptions,
|
||||
ratios: (ratios || []),
|
||||
ratioArrayPosition: ((ratios.length > 0) ? 0 : -1),
|
||||
ratios: ratios || [],
|
||||
ratioArrayPosition: ratios.length > 0 ? 0 : -1,
|
||||
});
|
||||
}
|
||||
|
||||
@ -118,48 +170,56 @@ export default class CameraScreenBase extends Component {
|
||||
}
|
||||
|
||||
renderFlashButton() {
|
||||
return !this.isCaptureRetakeMode() &&
|
||||
<TouchableOpacity style={{ paddingHorizontal: 15 }} onPress={() => this.onSetFlash(FLASH_MODE_AUTO)}>
|
||||
<Image
|
||||
style={{ flex: 1, justifyContent: 'center' }}
|
||||
source={this.state.flashData.image}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</TouchableOpacity>;
|
||||
return (
|
||||
!this.isCaptureRetakeMode() && (
|
||||
<TouchableOpacity style={{ paddingHorizontal: 15 }} onPress={() => this.onSetFlash()}>
|
||||
<Image
|
||||
style={{ flex: 1, justifyContent: 'center' }}
|
||||
source={this.state.flashData.image}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
renderSwitchCameraButton() {
|
||||
return (this.props.cameraFlipImage && !this.isCaptureRetakeMode()) &&
|
||||
<TouchableOpacity style={{ paddingHorizontal: 15 }} onPress={this.onSwitchCameraPressed}>
|
||||
<Image
|
||||
style={{ flex: 1, justifyContent: 'center' }}
|
||||
source={this.props.cameraFlipImage}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</TouchableOpacity>;
|
||||
return (
|
||||
this.props.cameraFlipImage &&
|
||||
!this.isCaptureRetakeMode() && (
|
||||
<TouchableOpacity style={{ paddingHorizontal: 15 }} onPress={this.onSwitchCameraPressed}>
|
||||
<Image
|
||||
style={{ flex: 1, justifyContent: 'center' }}
|
||||
source={this.props.cameraFlipImage}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
renderTopButtons() {
|
||||
return !this.props.hideControls && (
|
||||
<SafeAreaView style={styles.topButtons}>
|
||||
{this.renderFlashButton()}
|
||||
{this.renderSwitchCameraButton()}
|
||||
</SafeAreaView>
|
||||
return (
|
||||
!this.props.hideControls && (
|
||||
<SafeAreaView style={styles.topButtons}>
|
||||
{this.renderFlashButton()}
|
||||
{this.renderSwitchCameraButton()}
|
||||
</SafeAreaView>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
renderCamera() {
|
||||
return (
|
||||
<View style={styles.cameraContainer}>
|
||||
{
|
||||
this.isCaptureRetakeMode() ?
|
||||
<Image
|
||||
style={{ flex: 1, justifyContent: 'flex-end' }}
|
||||
source={{ uri: this.state.imageCaptured.uri }}
|
||||
/> :
|
||||
<CameraKitCamera
|
||||
ref={(cam) => this.camera = cam}
|
||||
{this.isCaptureRetakeMode() ? (
|
||||
<Image style={{ flex: 1, justifyContent: 'flex-end' }} source={{ uri: this.state.imageCaptured.uri }} />
|
||||
) : (
|
||||
<Camera
|
||||
ref={(cam) => (this.camera = cam)}
|
||||
type={this.state.type}
|
||||
style={{ flex: 1, justifyContent: 'flex-end' }}
|
||||
flashMode={this.state.flashData.mode}
|
||||
cameraOptions={this.state.cameraOptions}
|
||||
saveToCameraRoll={!this.props.allowCaptureRetake}
|
||||
showFrame={this.props.showFrame}
|
||||
@ -167,10 +227,10 @@ export default class CameraScreenBase extends Component {
|
||||
laserColor={this.props.laserColor}
|
||||
frameColor={this.props.frameColor}
|
||||
surfaceColor={this.props.surfaceColor}
|
||||
onReadCode = {this.props.onReadCode}
|
||||
scannerOptions = {this.state.scannerOptions}
|
||||
onReadCode={this.props.onReadCode}
|
||||
scannerOptions={this.state.scannerOptions}
|
||||
/>
|
||||
}
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@ -187,23 +247,19 @@ export default class CameraScreenBase extends Component {
|
||||
}
|
||||
|
||||
renderCaptureButton() {
|
||||
return (this.props.captureButtonImage && !this.isCaptureRetakeMode()) &&
|
||||
<View style={styles.captureButtonContainer}>
|
||||
<TouchableOpacity
|
||||
onPress={() => this.onCaptureImagePressed()}
|
||||
>
|
||||
<Image
|
||||
source={this.props.captureButtonImage}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
<View style={styles.textNumberContainer}>
|
||||
<Text>
|
||||
{this.numberOfImagesTaken()}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
</TouchableOpacity>
|
||||
</View >;
|
||||
return (
|
||||
this.props.captureButtonImage &&
|
||||
!this.isCaptureRetakeMode() && (
|
||||
<View style={styles.captureButtonContainer}>
|
||||
<TouchableOpacity onPress={() => this.onCaptureImagePressed()}>
|
||||
<Image source={this.props.captureButtonImage} resizeMode="contain" />
|
||||
<View style={styles.textNumberContainer}>
|
||||
<Text>{this.numberOfImagesTaken()}</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
renderRatioStrip() {
|
||||
@ -239,7 +295,7 @@ export default class CameraScreenBase extends Component {
|
||||
this.setState({ imageCaptured: undefined });
|
||||
}
|
||||
} else {
|
||||
this.sendBottomButtonPressedAction(type, captureRetakeMode);
|
||||
this.sendBottomButtonPressedAction(type, captureRetakeMode, null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,30 +313,31 @@ export default class CameraScreenBase extends Component {
|
||||
</TouchableOpacity>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.bottomContainerGap} />
|
||||
);
|
||||
return <View style={styles.bottomContainerGap} />;
|
||||
}
|
||||
}
|
||||
|
||||
renderBottomButtons() {
|
||||
return !this.props.hideControls && (
|
||||
<SafeAreaView style={[styles.bottomButtons, { backgroundColor: '#ffffff00' }]}>
|
||||
{this.renderBottomButton('left')}
|
||||
{this.renderCaptureButton()}
|
||||
</SafeAreaView>
|
||||
return (
|
||||
!this.props.hideControls && (
|
||||
<SafeAreaView style={[styles.bottomButtons, { backgroundColor: '#ffffff00' }]}>
|
||||
{/*this.renderBottomButton('left')*/}
|
||||
{this.renderCaptureButton()}
|
||||
</SafeAreaView>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
onSwitchCameraPressed() {
|
||||
this.camera.changeCamera();
|
||||
const direction = this.state.type === CameraType.Back ? CameraType.Front : CameraType.Back;
|
||||
console.log('SWITCH: ' + direction);
|
||||
this.setState({ type: direction });
|
||||
}
|
||||
|
||||
async onSetFlash() {
|
||||
this.currentFlashArrayPosition = (this.currentFlashArrayPosition + 1) % 3;
|
||||
const newFlashData = this.flashArray[this.currentFlashArrayPosition];
|
||||
this.setState({ flashData: newFlashData });
|
||||
this.camera.setFlashMode(newFlashData.mode);
|
||||
}
|
||||
|
||||
onSetTorch() {
|
||||
@ -296,73 +353,83 @@ export default class CameraScreenBase extends Component {
|
||||
this.setState({ imageCaptured: image });
|
||||
} else {
|
||||
if (image) {
|
||||
this.setState({ captured: true, imageCaptured: image, captureImages: _.concat(this.state.captureImages, image) });
|
||||
this.setState({
|
||||
captured: true,
|
||||
imageCaptured: image,
|
||||
captureImages: _.concat(this.state.captureImages, image),
|
||||
});
|
||||
}
|
||||
this.sendBottomButtonPressedAction('capture', false, image);
|
||||
}
|
||||
}
|
||||
|
||||
onRatioButtonPressed() {
|
||||
const newRatiosArrayPosition = ((this.state.ratioArrayPosition + 1) % this.state.ratios.length);
|
||||
const newCameraOptions = _.update(this.state.cameraOptions, 'ratioOverlay', (val) => this.state.ratios[newRatiosArrayPosition]);
|
||||
const newRatiosArrayPosition = (this.state.ratioArrayPosition + 1) % this.state.ratios.length;
|
||||
const newCameraOptions = _.update(
|
||||
this.state.cameraOptions,
|
||||
'ratioOverlay',
|
||||
(val) => this.state.ratios[newRatiosArrayPosition],
|
||||
);
|
||||
this.setState({ ratioArrayPosition: newRatiosArrayPosition, cameraOptions: newCameraOptions });
|
||||
}
|
||||
|
||||
render() {
|
||||
throw ('Implemented in CameraKitCameraScreen!');
|
||||
throw 'Implemented in CameraScreen!';
|
||||
}
|
||||
}
|
||||
|
||||
import styleObject from './CameraKitCameraScreenStyleObject';
|
||||
const styles = StyleSheet.create(_.merge(styleObject, {
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
fontSize: 20,
|
||||
},
|
||||
ratioBestText: {
|
||||
color: 'white',
|
||||
fontSize: 18,
|
||||
},
|
||||
ratioText: {
|
||||
color: '#ffc233',
|
||||
fontSize: 18,
|
||||
},
|
||||
topButtons: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
paddingTop: 8,
|
||||
paddingBottom: 0,
|
||||
},
|
||||
cameraContainer: {
|
||||
flex: 10,
|
||||
flexDirection: 'column',
|
||||
},
|
||||
captureButtonContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
textNumberContainer: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
bottomButton: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 10,
|
||||
},
|
||||
bottomContainerGap: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
padding: 10,
|
||||
},
|
||||
}));
|
||||
import styleObject from './CameraScreenStyleObject';
|
||||
const styles = StyleSheet.create(
|
||||
_.merge(styleObject, {
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
fontSize: 20,
|
||||
},
|
||||
ratioBestText: {
|
||||
color: 'white',
|
||||
fontSize: 18,
|
||||
},
|
||||
ratioText: {
|
||||
color: '#ffc233',
|
||||
fontSize: 18,
|
||||
},
|
||||
topButtons: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
paddingTop: 8,
|
||||
paddingBottom: 0,
|
||||
},
|
||||
cameraContainer: {
|
||||
flex: 10,
|
||||
flexDirection: 'column',
|
||||
},
|
||||
captureButtonContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
textNumberContainer: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
bottomButton: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: 10,
|
||||
},
|
||||
bottomContainerGap: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
padding: 10,
|
||||
},
|
||||
}),
|
||||
);
|
||||
@ -6,7 +6,8 @@ const styleObject = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width, height,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
bottomButtons: {
|
||||
flex: 2,
|
||||
6
src/CameraScreen/index.ts
Normal file
6
src/CameraScreen/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import CameraScreen from './CameraScreen';
|
||||
import CameraScreenBase from './CameraScreenBase';
|
||||
|
||||
export default CameraScreen;
|
||||
|
||||
export { CameraScreenBase };
|
||||
17
src/index.js
17
src/index.js
@ -1,17 +0,0 @@
|
||||
import { NativeModules } from "react-native";
|
||||
|
||||
import CameraKitGallery from "./CameraKitGallery";
|
||||
import CameraKitCamera from "./CameraKitCamera";
|
||||
import CameraKitGalleryView from "./CameraKitGalleryView";
|
||||
import CameraKitCameraScreen from "./CameraScreen/CameraKitCameraScreen";
|
||||
|
||||
const { CameraKit } = NativeModules;
|
||||
|
||||
export default CameraKit;
|
||||
|
||||
export {
|
||||
CameraKitGallery,
|
||||
CameraKitCamera,
|
||||
CameraKitGalleryView,
|
||||
CameraKitCameraScreen,
|
||||
};
|
||||
10
src/index.ts
Normal file
10
src/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
import Camera from './Camera';
|
||||
import CameraScreen from './CameraScreen/CameraScreen';
|
||||
|
||||
const { CameraKit } = NativeModules;
|
||||
|
||||
export default CameraKit;
|
||||
|
||||
export { Camera, CameraScreen };
|
||||
23
tsconfig.json
Normal file
23
tsconfig.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react",
|
||||
"lib": ["es6"],
|
||||
"moduleResolution": "node",
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"target": "esnext",
|
||||
"resolveJsonModule": true, // Required for JSON files
|
||||
"skipLibCheck": true, // Skips *.d.ts files
|
||||
"strictFunctionTypes": false,
|
||||
"noImplicitAny": false,
|
||||
"outDir": "./lib",
|
||||
"baseUrl": "app",
|
||||
"paths": {
|
||||
"*": ["*", "*.ios", "*.android"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "babel.config.js", "metro.config.js", "jest.config.js", "**/*.json", "**/*.spec.ts"]
|
||||
}
|
||||
10
yarn.lock
10
yarn.lock
@ -1250,6 +1250,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||
|
||||
"@types/lodash@^4.14.165":
|
||||
version "4.14.165"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.165.tgz#74d55d947452e2de0742bad65270433b63a8c30f"
|
||||
integrity sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg==
|
||||
|
||||
"@types/node@*":
|
||||
version "14.14.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.2.tgz#d25295f9e4ca5989a2c610754dc02a9721235eeb"
|
||||
@ -7370,6 +7375,11 @@ typedarray@^0.0.6:
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
typescript@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9"
|
||||
integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ==
|
||||
|
||||
ua-parser-js@^0.7.18:
|
||||
version "0.7.22"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.22.tgz#960df60a5f911ea8f1c818f3747b99c6e177eae3"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user