expo-camera 15.0.2 → 15.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/camera/CameraExceptions.kt +3 -0
- package/android/src/main/java/expo/modules/camera/CameraViewModule.kt +30 -0
- package/android/src/main/java/expo/modules/camera/analyzers/BarcodeScannerResultSerializer.kt +48 -0
- package/android/src/main/java/expo/modules/camera/analyzers/MLKitBarcodeAnalyzer.kt +102 -0
- package/build/index.d.ts +13 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +14 -0
- package/build/index.js.map +1 -1
- package/ios/CameraViewModule.swift +31 -0
- package/ios/Common/BarcodeExceptions.swift +19 -0
- package/ios/Common/BarcodeUtils.swift +70 -0
- package/ios/Common/ExpoCameraUtils.swift +1 -1
- package/package.json +2 -2
- package/src/index.ts +19 -0
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 15.0.3 — 2024-04-29
|
|
14
|
+
|
|
15
|
+
### 🎉 New features
|
|
16
|
+
|
|
17
|
+
- Support scanning barcodes from a provided image URL. ([#28445](https://github.com/expo/expo/pull/28445) by [@alanjhughes](https://github.com/alanjhughes))
|
|
18
|
+
|
|
13
19
|
## 15.0.2 — 2024-04-24
|
|
14
20
|
|
|
15
21
|
### 🐛 Bug fixes
|
package/android/build.gradle
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
apply plugin: 'com.android.library'
|
|
2
2
|
|
|
3
3
|
group = 'host.exp.exponent'
|
|
4
|
-
version = '15.0.
|
|
4
|
+
version = '15.0.3'
|
|
5
5
|
|
|
6
6
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
7
7
|
apply from: expoModulesCorePlugin
|
|
@@ -14,7 +14,7 @@ android {
|
|
|
14
14
|
namespace "expo.modules.camera"
|
|
15
15
|
defaultConfig {
|
|
16
16
|
versionCode 32
|
|
17
|
-
versionName "15.0.
|
|
17
|
+
versionName "15.0.3"
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -6,4 +6,7 @@ class CameraExceptions {
|
|
|
6
6
|
class ImageCaptureFailed : CodedException(message = "Failed to capture image")
|
|
7
7
|
|
|
8
8
|
class VideoRecordingFailed(cause: String?) : CodedException("Video recording failed: $cause")
|
|
9
|
+
|
|
10
|
+
class ImageRetrievalException(url: String) :
|
|
11
|
+
CodedException("Could not get the image from given url: '$url'")
|
|
9
12
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
package expo.modules.camera
|
|
2
2
|
|
|
3
3
|
import android.Manifest
|
|
4
|
+
import android.graphics.Bitmap
|
|
4
5
|
import android.util.Log
|
|
6
|
+
import expo.modules.camera.analyzers.BarCodeScannerResultSerializer
|
|
7
|
+
import expo.modules.camera.analyzers.MLKitBarCodeScanner
|
|
5
8
|
import expo.modules.camera.records.BarcodeSettings
|
|
9
|
+
import expo.modules.camera.records.BarcodeType
|
|
6
10
|
import expo.modules.camera.records.CameraMode
|
|
7
11
|
import expo.modules.camera.records.CameraType
|
|
8
12
|
import expo.modules.camera.records.FlashMode
|
|
@@ -10,6 +14,7 @@ import expo.modules.camera.records.VideoQuality
|
|
|
10
14
|
import expo.modules.camera.tasks.ResolveTakenPicture
|
|
11
15
|
import expo.modules.core.errors.ModuleDestroyedException
|
|
12
16
|
import expo.modules.core.utilities.EmulatorUtilities
|
|
17
|
+
import expo.modules.interfaces.imageloader.ImageLoaderInterface
|
|
13
18
|
import expo.modules.interfaces.permissions.Permissions
|
|
14
19
|
import expo.modules.kotlin.Promise
|
|
15
20
|
import expo.modules.kotlin.exception.Exceptions
|
|
@@ -33,6 +38,7 @@ val cameraEvents = arrayOf(
|
|
|
33
38
|
|
|
34
39
|
class CameraViewModule : Module() {
|
|
35
40
|
private val moduleScope = CoroutineScope(Dispatchers.Main)
|
|
41
|
+
|
|
36
42
|
override fun definition() = ModuleDefinition {
|
|
37
43
|
Name("ExpoCamera")
|
|
38
44
|
|
|
@@ -70,6 +76,30 @@ class CameraViewModule : Module() {
|
|
|
70
76
|
)
|
|
71
77
|
}
|
|
72
78
|
|
|
79
|
+
AsyncFunction("scanFromURLAsync") { url: String, barcodeTypes: List<BarcodeType>, promise: Promise ->
|
|
80
|
+
appContext.imageLoader?.loadImageForManipulationFromURL(
|
|
81
|
+
url,
|
|
82
|
+
object : ImageLoaderInterface.ResultListener {
|
|
83
|
+
override fun onSuccess(bitmap: Bitmap) {
|
|
84
|
+
val scanner = MLKitBarCodeScanner()
|
|
85
|
+
val formats = barcodeTypes.map { it.mapToBarcode() }
|
|
86
|
+
scanner.setSettings(formats)
|
|
87
|
+
|
|
88
|
+
moduleScope.launch {
|
|
89
|
+
val barcodes = scanner.scan(bitmap)
|
|
90
|
+
.filter { formats.contains(it.type) }
|
|
91
|
+
.map { BarCodeScannerResultSerializer.toBundle(it, 1.0f) }
|
|
92
|
+
promise.resolve(barcodes)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
override fun onFailure(cause: Throwable?) {
|
|
97
|
+
promise.reject(CameraExceptions.ImageRetrievalException(url))
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
73
103
|
OnDestroy {
|
|
74
104
|
try {
|
|
75
105
|
moduleScope.cancel(ModuleDestroyedException())
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package expo.modules.camera.analyzers
|
|
2
|
+
|
|
3
|
+
import android.os.Bundle
|
|
4
|
+
import android.util.Pair
|
|
5
|
+
import expo.modules.interfaces.barcodescanner.BarCodeScannerResult
|
|
6
|
+
|
|
7
|
+
object BarCodeScannerResultSerializer {
|
|
8
|
+
fun toBundle(result: BarCodeScannerResult, density: Float) =
|
|
9
|
+
Bundle().apply {
|
|
10
|
+
putString("data", result.value)
|
|
11
|
+
putString("raw", result.raw)
|
|
12
|
+
putInt("type", result.type)
|
|
13
|
+
val cornerPointsAndBoundingBox = getCornerPointsAndBoundingBox(result.cornerPoints, result.boundingBox, density)
|
|
14
|
+
putParcelableArrayList("cornerPoints", cornerPointsAndBoundingBox.first)
|
|
15
|
+
putBundle("bounds", cornerPointsAndBoundingBox.second)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
private fun getCornerPointsAndBoundingBox(
|
|
19
|
+
cornerPoints: List<Int>,
|
|
20
|
+
boundingBox: BarCodeScannerResult.BoundingBox,
|
|
21
|
+
density: Float
|
|
22
|
+
): Pair<ArrayList<Bundle>, Bundle> {
|
|
23
|
+
val convertedCornerPoints = ArrayList<Bundle>()
|
|
24
|
+
for (i in cornerPoints.indices step 2) {
|
|
25
|
+
val x = cornerPoints[i].toFloat() / density
|
|
26
|
+
val y = cornerPoints[i + 1].toFloat() / density
|
|
27
|
+
|
|
28
|
+
convertedCornerPoints.add(getPoint(x, y))
|
|
29
|
+
}
|
|
30
|
+
val boundingBoxBundle = Bundle().apply {
|
|
31
|
+
putParcelable("origin", getPoint(boundingBox.x.toFloat() / density, boundingBox.y.toFloat() / density))
|
|
32
|
+
putParcelable("size", getSize(boundingBox.width.toFloat() / density, boundingBox.height.toFloat() / density))
|
|
33
|
+
}
|
|
34
|
+
return Pair(convertedCornerPoints, boundingBoxBundle)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private fun getSize(width: Float, height: Float) =
|
|
38
|
+
Bundle().apply {
|
|
39
|
+
putFloat("width", width)
|
|
40
|
+
putFloat("height", height)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private fun getPoint(x: Float, y: Float) =
|
|
44
|
+
Bundle().apply {
|
|
45
|
+
putFloat("x", x)
|
|
46
|
+
putFloat("y", y)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
package expo.modules.camera.analyzers
|
|
2
|
+
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import com.google.android.gms.tasks.Task
|
|
6
|
+
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
|
7
|
+
import com.google.mlkit.vision.barcode.BarcodeScanning
|
|
8
|
+
import com.google.mlkit.vision.barcode.common.Barcode
|
|
9
|
+
import com.google.mlkit.vision.common.InputImage
|
|
10
|
+
import expo.modules.interfaces.barcodescanner.BarCodeScannerResult
|
|
11
|
+
import kotlinx.coroutines.Dispatchers
|
|
12
|
+
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
13
|
+
import kotlinx.coroutines.withContext
|
|
14
|
+
import kotlin.coroutines.resume
|
|
15
|
+
import kotlin.coroutines.resumeWithException
|
|
16
|
+
|
|
17
|
+
class MLKitBarCodeScanner {
|
|
18
|
+
private var barCodeTypes: List<Int>? = null
|
|
19
|
+
private var barcodeScannerOptions =
|
|
20
|
+
BarcodeScannerOptions.Builder()
|
|
21
|
+
.setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS)
|
|
22
|
+
.build()
|
|
23
|
+
private var barcodeScanner = BarcodeScanning.getClient(barcodeScannerOptions)
|
|
24
|
+
|
|
25
|
+
suspend fun scan(bitmap: Bitmap): List<BarCodeScannerResult> = withContext(Dispatchers.IO) {
|
|
26
|
+
val inputImage = InputImage.fromBitmap(bitmap, 0)
|
|
27
|
+
try {
|
|
28
|
+
val result: List<Barcode> = barcodeScanner.process(inputImage).await()
|
|
29
|
+
val results = mutableListOf<BarCodeScannerResult>()
|
|
30
|
+
if (result.isEmpty()) {
|
|
31
|
+
return@withContext results
|
|
32
|
+
}
|
|
33
|
+
for (barcode in result) {
|
|
34
|
+
val raw = barcode.rawValue ?: barcode.rawBytes?.let { String(it) }
|
|
35
|
+
val value = if (barcode.valueType == Barcode.TYPE_CONTACT_INFO) {
|
|
36
|
+
raw
|
|
37
|
+
} else {
|
|
38
|
+
barcode.displayValue
|
|
39
|
+
}
|
|
40
|
+
val cornerPoints = mutableListOf<Int>()
|
|
41
|
+
barcode.cornerPoints?.let { points ->
|
|
42
|
+
for (point in points) {
|
|
43
|
+
cornerPoints.addAll(listOf(point.x, point.y))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
results.add(BarCodeScannerResult(barcode.format, value, raw, cornerPoints, inputImage.height, inputImage.width))
|
|
48
|
+
}
|
|
49
|
+
return@withContext results
|
|
50
|
+
} catch (e: Exception) {
|
|
51
|
+
Log.e(TAG, "Failed to detect barcode: " + e.message)
|
|
52
|
+
return@withContext emptyList()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fun setSettings(formats: List<Int>) {
|
|
57
|
+
if (areNewAndOldBarCodeTypesEqual(formats)) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
val barcodeFormats = formats.reduce { acc, it ->
|
|
61
|
+
acc or it
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
barCodeTypes = formats
|
|
65
|
+
barcodeScannerOptions = BarcodeScannerOptions.Builder()
|
|
66
|
+
.setBarcodeFormats(barcodeFormats)
|
|
67
|
+
.build()
|
|
68
|
+
barcodeScanner = BarcodeScanning.getClient(barcodeScannerOptions)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private fun areNewAndOldBarCodeTypesEqual(newBarCodeTypes: List<Int>): Boolean {
|
|
72
|
+
barCodeTypes?.run {
|
|
73
|
+
// create distinct-values sets
|
|
74
|
+
val prevTypesSet = toHashSet()
|
|
75
|
+
val nextTypesSet = newBarCodeTypes.toHashSet()
|
|
76
|
+
|
|
77
|
+
// sets sizes are equal -> possible content equality
|
|
78
|
+
if (prevTypesSet.size == nextTypesSet.size) {
|
|
79
|
+
prevTypesSet.removeAll(nextTypesSet)
|
|
80
|
+
// every element from new set was in previous one -> sets are equal
|
|
81
|
+
return prevTypesSet.isEmpty()
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
companion object {
|
|
88
|
+
private val TAG = MLKitBarCodeScanner::class.java.simpleName
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
suspend fun <T> Task<T>.await(): T = suspendCancellableCoroutine { continuation ->
|
|
93
|
+
addOnSuccessListener { result ->
|
|
94
|
+
continuation.resume(result)
|
|
95
|
+
}
|
|
96
|
+
addOnFailureListener { exception ->
|
|
97
|
+
continuation.resumeWithException(exception)
|
|
98
|
+
}
|
|
99
|
+
addOnCanceledListener {
|
|
100
|
+
continuation.cancel()
|
|
101
|
+
}
|
|
102
|
+
}
|
package/build/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BarcodeScanningResult, BarcodeType } from './Camera.types';
|
|
1
2
|
import { PermissionResponse } from './legacy/Camera.types';
|
|
2
3
|
export { default as CameraView } from './CameraView';
|
|
3
4
|
/**
|
|
@@ -42,6 +43,17 @@ declare function requestMicrophonePermissionsAsync(): Promise<PermissionResponse
|
|
|
42
43
|
* ```
|
|
43
44
|
*/
|
|
44
45
|
export declare const useMicrophonePermissions: (options?: import("expo-modules-core").PermissionHookOptions<object> | undefined) => [PermissionResponse | null, () => Promise<PermissionResponse>, () => Promise<PermissionResponse>];
|
|
46
|
+
/**
|
|
47
|
+
* Scan bar codes from the image at the given URL.
|
|
48
|
+
* @param url URL to get the image from.
|
|
49
|
+
* @param barcodeTypes An array of bar code types. Defaults to all supported bar code types on
|
|
50
|
+
* the platform.
|
|
51
|
+
* > __Note:__ Only QR codes are supported on iOS.
|
|
52
|
+
* On android, the barcode should take up the majority of the image for best results.
|
|
53
|
+
* @return A possibly empty array of objects of the `BarcodeScanningResult` shape, where the type
|
|
54
|
+
* refers to the barcode type that was scanned and the data is the information encoded in the barcode.
|
|
55
|
+
*/
|
|
56
|
+
export declare function scanFromURLAsync(url: string, barcodeTypes?: BarcodeType[]): Promise<BarcodeScanningResult>;
|
|
45
57
|
export * from './Camera.types';
|
|
46
58
|
/**
|
|
47
59
|
* @hidden
|
|
@@ -51,5 +63,6 @@ export declare const Camera: {
|
|
|
51
63
|
requestCameraPermissionsAsync: typeof requestCameraPermissionsAsync;
|
|
52
64
|
getMicrophonePermissionsAsync: typeof getMicrophonePermissionsAsync;
|
|
53
65
|
requestMicrophonePermissionsAsync: typeof requestMicrophonePermissionsAsync;
|
|
66
|
+
scanFromURLAsync: typeof scanFromURLAsync;
|
|
54
67
|
};
|
|
55
68
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AAGrD;;;GAGG;AACH,iBAAe,yBAAyB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAEtE;AAGD;;;;GAIG;AACH,iBAAe,6BAA6B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE1E;AAGD;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,wLAG/B,CAAC;AAGH;;;GAGG;AACH,iBAAe,6BAA6B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE1E;AAGD;;;;GAIG;AACH,iBAAe,iCAAiC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAE9E;AAGD;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB,wLAGnC,CAAC;AAEH;;;;;;;;;GASG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,YAAY,GAAE,WAAW,EAAW,GACnC,OAAO,CAAC,qBAAqB,CAAC,CAEhC;AAED,cAAc,gBAAgB,CAAC;AAE/B;;GAEG;AACH,eAAO,MAAM,MAAM;;;;;;CAMlB,CAAC"}
|
package/build/index.js
CHANGED
|
@@ -63,6 +63,19 @@ export const useMicrophonePermissions = createPermissionHook({
|
|
|
63
63
|
getMethod: getMicrophonePermissionsAsync,
|
|
64
64
|
requestMethod: requestMicrophonePermissionsAsync,
|
|
65
65
|
});
|
|
66
|
+
/**
|
|
67
|
+
* Scan bar codes from the image at the given URL.
|
|
68
|
+
* @param url URL to get the image from.
|
|
69
|
+
* @param barcodeTypes An array of bar code types. Defaults to all supported bar code types on
|
|
70
|
+
* the platform.
|
|
71
|
+
* > __Note:__ Only QR codes are supported on iOS.
|
|
72
|
+
* On android, the barcode should take up the majority of the image for best results.
|
|
73
|
+
* @return A possibly empty array of objects of the `BarcodeScanningResult` shape, where the type
|
|
74
|
+
* refers to the barcode type that was scanned and the data is the information encoded in the barcode.
|
|
75
|
+
*/
|
|
76
|
+
export async function scanFromURLAsync(url, barcodeTypes = ['qr']) {
|
|
77
|
+
return CameraManager.scanFromURLAsync(url, barcodeTypes);
|
|
78
|
+
}
|
|
66
79
|
export * from './Camera.types';
|
|
67
80
|
/**
|
|
68
81
|
* @hidden
|
|
@@ -72,5 +85,6 @@ export const Camera = {
|
|
|
72
85
|
requestCameraPermissionsAsync,
|
|
73
86
|
getMicrophonePermissionsAsync,
|
|
74
87
|
requestMicrophonePermissionsAsync,
|
|
88
|
+
scanFromURLAsync,
|
|
75
89
|
};
|
|
76
90
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD,OAAO,aAAa,MAAM,qBAAqB,CAAC;AAGhD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AAErD,cAAc;AACd;;;GAGG;AACH,KAAK,UAAU,yBAAyB;IACtC,OAAO,aAAa,CAAC,yBAAyB,EAAE,CAAC;AACnD,CAAC;AAED,cAAc;AACd;;;;GAIG;AACH,KAAK,UAAU,6BAA6B;IAC1C,OAAO,aAAa,CAAC,6BAA6B,EAAE,CAAC;AACvD,CAAC;AAED,cAAc;AACd;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,oBAAoB,CAAC;IACvD,SAAS,EAAE,yBAAyB;IACpC,aAAa,EAAE,6BAA6B;CAC7C,CAAC,CAAC;AAEH,cAAc;AACd;;;GAGG;AACH,KAAK,UAAU,6BAA6B;IAC1C,OAAO,aAAa,CAAC,6BAA6B,EAAE,CAAC;AACvD,CAAC;AAED,cAAc;AACd;;;;GAIG;AACH,KAAK,UAAU,iCAAiC;IAC9C,OAAO,aAAa,CAAC,iCAAiC,EAAE,CAAC;AAC3D,CAAC;AAED,cAAc;AACd;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,oBAAoB,CAAC;IAC3D,SAAS,EAAE,6BAA6B;IACxC,aAAa,EAAE,iCAAiC;CACjD,CAAC,CAAC;AAEH;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAW,EACX,eAA8B,CAAC,IAAI,CAAC;IAEpC,OAAO,aAAa,CAAC,gBAAgB,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;AAC3D,CAAC;AAED,cAAc,gBAAgB,CAAC;AAE/B;;GAEG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,yBAAyB;IACzB,6BAA6B;IAC7B,6BAA6B;IAC7B,iCAAiC;IACjC,gBAAgB;CACjB,CAAC","sourcesContent":["import { createPermissionHook } from 'expo-modules-core';\n\nimport { BarcodeScanningResult, BarcodeType } from './Camera.types';\nimport CameraManager from './ExpoCameraManager';\nimport { PermissionResponse } from './legacy/Camera.types';\n\nexport { default as CameraView } from './CameraView';\n\n// @needsAudit\n/**\n * Checks user's permissions for accessing camera.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function getCameraPermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.getCameraPermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Asks the user to grant permissions for accessing camera.\n * On iOS this will require apps to specify an `NSCameraUsageDescription` entry in the **Info.plist**.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function requestCameraPermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.requestCameraPermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Check or request permissions to access the camera.\n * This uses both `requestCameraPermissionsAsync` and `getCameraPermissionsAsync` to interact with the permissions.\n *\n * @example\n * ```ts\n * const [status, requestPermission] = useCameraPermissions();\n * ```\n */\nexport const useCameraPermissions = createPermissionHook({\n getMethod: getCameraPermissionsAsync,\n requestMethod: requestCameraPermissionsAsync,\n});\n\n// @needsAudit\n/**\n * Checks user's permissions for accessing microphone.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function getMicrophonePermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.getMicrophonePermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Asks the user to grant permissions for accessing the microphone.\n * On iOS this will require apps to specify an `NSMicrophoneUsageDescription` entry in the **Info.plist**.\n * @return A promise that resolves to an object of type [PermissionResponse](#permissionresponse).\n */\nasync function requestMicrophonePermissionsAsync(): Promise<PermissionResponse> {\n return CameraManager.requestMicrophonePermissionsAsync();\n}\n\n// @needsAudit\n/**\n * Check or request permissions to access the microphone.\n * This uses both `requestMicrophonePermissionsAsync` and `getMicrophonePermissionsAsync` to interact with the permissions.\n *\n * @example\n * ```ts\n * const [status, requestPermission] = Camera.useMicrophonePermissions();\n * ```\n */\nexport const useMicrophonePermissions = createPermissionHook({\n getMethod: getMicrophonePermissionsAsync,\n requestMethod: requestMicrophonePermissionsAsync,\n});\n\n/**\n * Scan bar codes from the image at the given URL.\n * @param url URL to get the image from.\n * @param barcodeTypes An array of bar code types. Defaults to all supported bar code types on\n * the platform.\n * > __Note:__ Only QR codes are supported on iOS.\n * On android, the barcode should take up the majority of the image for best results.\n * @return A possibly empty array of objects of the `BarcodeScanningResult` shape, where the type\n * refers to the barcode type that was scanned and the data is the information encoded in the barcode.\n */\nexport async function scanFromURLAsync(\n url: string,\n barcodeTypes: BarcodeType[] = ['qr']\n): Promise<BarcodeScanningResult> {\n return CameraManager.scanFromURLAsync(url, barcodeTypes);\n}\n\nexport * from './Camera.types';\n\n/**\n * @hidden\n */\nexport const Camera = {\n getCameraPermissionsAsync,\n requestCameraPermissionsAsync,\n getMicrophonePermissionsAsync,\n requestMicrophonePermissionsAsync,\n scanFromURLAsync,\n};\n"]}
|
|
@@ -37,6 +37,37 @@ public final class CameraViewModule: Module, ScannerResultHandler {
|
|
|
37
37
|
return false
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
AsyncFunction("scanFromURLAsync") { (url: URL, _: [BarcodeType], promise: Promise) in
|
|
41
|
+
guard let imageLoader = appContext?.imageLoader else {
|
|
42
|
+
throw ImageLoaderNotFound()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
imageLoader.loadImage(for: url) { error, image in
|
|
46
|
+
if error != nil {
|
|
47
|
+
promise.reject(FailedToLoadImage())
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
guard let cgImage = image?.cgImage else {
|
|
52
|
+
promise.reject(FailedToLoadImage())
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
guard let detector = CIDetector(
|
|
57
|
+
ofType: CIDetectorTypeQRCode,
|
|
58
|
+
context: nil,
|
|
59
|
+
options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]
|
|
60
|
+
) else {
|
|
61
|
+
promise.reject(InitScannerFailed())
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let ciImage = CIImage(cgImage: cgImage)
|
|
66
|
+
let features = detector.features(in: ciImage)
|
|
67
|
+
promise.resolve(BarcodeUtils.getResultFrom(features))
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
40
71
|
// swiftlint:disable:next closure_body_length
|
|
41
72
|
View(CameraView.self) {
|
|
42
73
|
Events(cameraNextEvents)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
internal class ImageLoaderNotFound: Exception {
|
|
4
|
+
override var reason: String {
|
|
5
|
+
"Image Loader module not found"
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
internal class FailedToLoadImage: Exception {
|
|
10
|
+
override var reason: String {
|
|
11
|
+
"Could not get the image"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
internal class InitScannerFailed: Exception {
|
|
16
|
+
override var reason: String {
|
|
17
|
+
"Could not initialize the barcode scanner"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
|
|
3
|
+
struct BarcodeUtils {
|
|
4
|
+
static func getResultFrom(_ features: [CIFeature]) -> [[AnyHashable: Any]?] {
|
|
5
|
+
var result = [[AnyHashable: Any]?]()
|
|
6
|
+
|
|
7
|
+
for feature in features {
|
|
8
|
+
if let qrCodeFeature = feature as? CIQRCodeFeature {
|
|
9
|
+
let item = ciQRCodeFeature(
|
|
10
|
+
codeFeature: qrCodeFeature
|
|
11
|
+
)
|
|
12
|
+
result.append(item)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return result
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static func ciQRCodeFeature(codeFeature: CIQRCodeFeature) -> [String: Any] {
|
|
20
|
+
var result: [String: Any] = [:]
|
|
21
|
+
result["type"] = "qr"
|
|
22
|
+
result["data"] = codeFeature.messageString
|
|
23
|
+
|
|
24
|
+
if !codeFeature.bounds.isEmpty {
|
|
25
|
+
result["cornerPoints"] = [
|
|
26
|
+
codeFeature.topLeft,
|
|
27
|
+
codeFeature.topRight,
|
|
28
|
+
codeFeature.bottomRight,
|
|
29
|
+
codeFeature.bottomLeft
|
|
30
|
+
].map { point in
|
|
31
|
+
[
|
|
32
|
+
"x": point.x,
|
|
33
|
+
"y": point.y
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let origin = codeFeature.bounds.origin
|
|
38
|
+
let size = codeFeature.bounds.size
|
|
39
|
+
|
|
40
|
+
result["bounds"] = [
|
|
41
|
+
"origin": [
|
|
42
|
+
"x": origin.x,
|
|
43
|
+
"y": origin.y
|
|
44
|
+
],
|
|
45
|
+
"size": [
|
|
46
|
+
"width": size.width,
|
|
47
|
+
"height": size.height
|
|
48
|
+
]
|
|
49
|
+
]
|
|
50
|
+
} else {
|
|
51
|
+
addEmptyCornerPoints(to: &result)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return result
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static func addEmptyCornerPoints(to result: inout [String: Any]) {
|
|
58
|
+
result["cornerPoints"] = []
|
|
59
|
+
result["bounds"] = [
|
|
60
|
+
"origin": [
|
|
61
|
+
"x": 0,
|
|
62
|
+
"y": 0
|
|
63
|
+
],
|
|
64
|
+
"size": [
|
|
65
|
+
"width": 0,
|
|
66
|
+
"height": 0
|
|
67
|
+
]
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-camera",
|
|
3
|
-
"version": "15.0.
|
|
3
|
+
"version": "15.0.3",
|
|
4
4
|
"description": "A React component that renders a preview for the device's either front or back camera. Camera's parameters like zoom, auto focus, white balance and flash mode are adjustable. With expo-camera, one can also take photos and record videos that are saved to the app's cache. Morever, the component is also capable of detecting faces and bar codes appearing on the preview.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -42,5 +42,5 @@
|
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"expo": "*"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "4a7cf0d0baf6dfc595d93f604945d2142e705a36"
|
|
46
46
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createPermissionHook } from 'expo-modules-core';
|
|
2
2
|
|
|
3
|
+
import { BarcodeScanningResult, BarcodeType } from './Camera.types';
|
|
3
4
|
import CameraManager from './ExpoCameraManager';
|
|
4
5
|
import { PermissionResponse } from './legacy/Camera.types';
|
|
5
6
|
|
|
@@ -73,6 +74,23 @@ export const useMicrophonePermissions = createPermissionHook({
|
|
|
73
74
|
requestMethod: requestMicrophonePermissionsAsync,
|
|
74
75
|
});
|
|
75
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Scan bar codes from the image at the given URL.
|
|
79
|
+
* @param url URL to get the image from.
|
|
80
|
+
* @param barcodeTypes An array of bar code types. Defaults to all supported bar code types on
|
|
81
|
+
* the platform.
|
|
82
|
+
* > __Note:__ Only QR codes are supported on iOS.
|
|
83
|
+
* On android, the barcode should take up the majority of the image for best results.
|
|
84
|
+
* @return A possibly empty array of objects of the `BarcodeScanningResult` shape, where the type
|
|
85
|
+
* refers to the barcode type that was scanned and the data is the information encoded in the barcode.
|
|
86
|
+
*/
|
|
87
|
+
export async function scanFromURLAsync(
|
|
88
|
+
url: string,
|
|
89
|
+
barcodeTypes: BarcodeType[] = ['qr']
|
|
90
|
+
): Promise<BarcodeScanningResult> {
|
|
91
|
+
return CameraManager.scanFromURLAsync(url, barcodeTypes);
|
|
92
|
+
}
|
|
93
|
+
|
|
76
94
|
export * from './Camera.types';
|
|
77
95
|
|
|
78
96
|
/**
|
|
@@ -83,4 +101,5 @@ export const Camera = {
|
|
|
83
101
|
requestCameraPermissionsAsync,
|
|
84
102
|
getMicrophonePermissionsAsync,
|
|
85
103
|
requestMicrophonePermissionsAsync,
|
|
104
|
+
scanFromURLAsync,
|
|
86
105
|
};
|