capacitor-camera-view 1.0.0

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.
Files changed (42) hide show
  1. package/CapacitorCameraView.podspec +17 -0
  2. package/LICENSE +201 -0
  3. package/Package.swift +28 -0
  4. package/README.md +654 -0
  5. package/android/build.gradle +79 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraView.kt +555 -0
  8. package/android/src/main/java/com/michaelwolz/capacitorcameraview/CameraViewPlugin.kt +227 -0
  9. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/BarcodeDetectionResult.kt +11 -0
  10. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraDevice.kt +14 -0
  11. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/CameraSessionConfiguration.kt +10 -0
  12. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/WebBoundingRect.kt +16 -0
  13. package/android/src/main/java/com/michaelwolz/capacitorcameraview/model/ZoomFactors.kt +14 -0
  14. package/android/src/main/java/com/michaelwolz/capacitorcameraview/utils.kt +86 -0
  15. package/android/src/main/res/.gitkeep +0 -0
  16. package/dist/docs.json +968 -0
  17. package/dist/esm/definitions.d.ts +378 -0
  18. package/dist/esm/definitions.js +2 -0
  19. package/dist/esm/definitions.js.map +1 -0
  20. package/dist/esm/index.d.ts +7 -0
  21. package/dist/esm/index.js +10 -0
  22. package/dist/esm/index.js.map +1 -0
  23. package/dist/esm/utils.d.ts +45 -0
  24. package/dist/esm/utils.js +108 -0
  25. package/dist/esm/utils.js.map +1 -0
  26. package/dist/esm/web.d.ts +108 -0
  27. package/dist/esm/web.js +406 -0
  28. package/dist/esm/web.js.map +1 -0
  29. package/dist/plugin.cjs.js +530 -0
  30. package/dist/plugin.cjs.js.map +1 -0
  31. package/dist/plugin.js +533 -0
  32. package/dist/plugin.js.map +1 -0
  33. package/ios/Sources/CameraViewPlugin/CameraError.swift +39 -0
  34. package/ios/Sources/CameraViewPlugin/CameraSessionConfiguration.swift +32 -0
  35. package/ios/Sources/CameraViewPlugin/CameraViewManager+BarcodeScan.swift +91 -0
  36. package/ios/Sources/CameraViewPlugin/CameraViewManager+PhotoCapture.swift +52 -0
  37. package/ios/Sources/CameraViewPlugin/CameraViewManager+VideoDataOutput.swift +78 -0
  38. package/ios/Sources/CameraViewPlugin/CameraViewManager.swift +633 -0
  39. package/ios/Sources/CameraViewPlugin/CameraViewPlugin.swift +295 -0
  40. package/ios/Sources/CameraViewPlugin/Utils.swift +56 -0
  41. package/ios/Tests/CameraViewPluginTests/CameraViewPluginTests.swift +15 -0
  42. package/package.json +94 -0
@@ -0,0 +1,227 @@
1
+ package com.michaelwolz.capacitorcameraview
2
+
3
+ import android.Manifest
4
+ import com.getcapacitor.JSArray
5
+ import com.getcapacitor.JSObject
6
+ import com.getcapacitor.PermissionState
7
+ import com.getcapacitor.Plugin
8
+ import com.getcapacitor.PluginCall
9
+ import com.getcapacitor.PluginMethod
10
+ import com.getcapacitor.annotation.CapacitorPlugin
11
+ import com.getcapacitor.annotation.Permission
12
+ import com.getcapacitor.annotation.PermissionCallback
13
+ import com.michaelwolz.capacitorcameraview.model.BarcodeDetectionResult
14
+
15
+ @CapacitorPlugin(
16
+ name = "CameraView",
17
+ permissions = [Permission(strings = [Manifest.permission.CAMERA], alias = "camera")]
18
+ )
19
+ class CameraViewPlugin : Plugin() {
20
+ private val implementation by lazy {
21
+ CameraView(this)
22
+ }
23
+
24
+ @PluginMethod
25
+ fun start(call: PluginCall) {
26
+ if (getPermissionState("camera") == PermissionState.GRANTED) {
27
+ startCamera(call)
28
+ } else {
29
+ requestPermissionForAlias("camera", call, "cameraPermsCallback")
30
+ }
31
+ }
32
+
33
+ @PermissionCallback
34
+ private fun cameraPermsCallback(call: PluginCall) {
35
+ if (getPermissionState("camera") == PermissionState.GRANTED) {
36
+ startCamera(call)
37
+ } else {
38
+ call.reject("Permission is required to take a picture")
39
+ }
40
+ }
41
+
42
+ private fun startCamera(call: PluginCall) {
43
+ implementation.startSession(
44
+ config = sessionConfigFromPluginCall(call),
45
+ callback = { error ->
46
+ if (error != null) {
47
+ call.reject("Failed to start camera preview: ${error.localizedMessage}", error)
48
+ } else {
49
+ call.resolve()
50
+ }
51
+ }
52
+ )
53
+ }
54
+
55
+ @PluginMethod
56
+ fun stop(call: PluginCall) {
57
+ implementation.stopSession { error ->
58
+ if (error != null) {
59
+ call.reject("Failed to stop camera preview: ${error.localizedMessage}", error)
60
+ } else {
61
+ call.resolve()
62
+ }
63
+ }
64
+ }
65
+
66
+ @PluginMethod
67
+ fun isRunning(call: PluginCall) {
68
+ val result = JSObject().apply {
69
+ put("isRunning", implementation.isRunning())
70
+ }
71
+ call.resolve(result)
72
+ }
73
+
74
+ @PluginMethod
75
+ fun capture(call: PluginCall) {
76
+ val quality = call.getInt("quality", 90)
77
+
78
+ if (quality !in 0..100) {
79
+ call.reject("Quality must be between 0 and 100")
80
+ return
81
+ }
82
+
83
+ implementation.capturePhoto(quality) { photo, error ->
84
+ when {
85
+ error != null -> call.reject("Failed to capture image: ${error.message}", error)
86
+ photo == null -> call.reject("No image data")
87
+ else -> call.resolve(JSObject().apply { put("photo", photo) })
88
+ }
89
+ }
90
+ }
91
+
92
+ @PluginMethod
93
+ fun captureSample(call: PluginCall) {
94
+ val quality = call.getInt("quality", 90)
95
+
96
+ if (quality !in 0..100) {
97
+ call.reject("Quality must be between 0 and 100")
98
+ return
99
+ }
100
+
101
+ implementation.captureSampleFromPreview(quality) { photo, error ->
102
+ when {
103
+ error != null -> call.reject("Failed to capture frame: ${error.message}", error)
104
+ photo == null -> call.reject("No frame data")
105
+ else -> call.resolve(JSObject().apply { put("photo", photo) })
106
+ }
107
+ }
108
+ }
109
+
110
+ @PluginMethod
111
+ fun getAvailableDevices(call: PluginCall) {
112
+ val devices = implementation.getAvailableDevices()
113
+ val devicesArray = JSArray().apply {
114
+ devices.forEach { device ->
115
+ put(JSObject().apply {
116
+ put("id", device.id)
117
+ put("name", device.name)
118
+ put("position", device.position)
119
+ })
120
+ }
121
+ }
122
+
123
+ call.resolve(JSObject().apply { put("devices", devicesArray) })
124
+ }
125
+
126
+ @PluginMethod
127
+ fun flipCamera(call: PluginCall) {
128
+ implementation.flipCamera { error ->
129
+ if (error != null) {
130
+ call.reject("Failed to flip camera: ${error.localizedMessage}", error)
131
+ } else {
132
+ call.resolve()
133
+ }
134
+ }
135
+ }
136
+
137
+ @PluginMethod
138
+ fun getZoom(call: PluginCall) {
139
+ implementation.getSupportedZoomFactors { zoomFactors ->
140
+ call.resolve(JSObject().apply {
141
+ put("min", zoomFactors.min)
142
+ put("max", zoomFactors.max)
143
+ put("current", zoomFactors.current)
144
+ })
145
+ }
146
+ }
147
+
148
+ @PluginMethod
149
+ fun setZoom(call: PluginCall) {
150
+ val level = call.getFloat("level")
151
+ if (level == null) {
152
+ call.reject("Zoom level must be provided")
153
+ return
154
+ }
155
+
156
+ implementation.setZoomFactor(level) { error ->
157
+ if (error != null) {
158
+ call.reject(error.localizedMessage)
159
+ } else {
160
+ call.resolve()
161
+ }
162
+ }
163
+ }
164
+
165
+ @PluginMethod
166
+ fun getFlashMode(call: PluginCall) {
167
+ call.resolve(JSObject().apply {
168
+ put("flashMode", implementation.getFlashMode())
169
+ })
170
+ }
171
+
172
+ @PluginMethod
173
+ fun getSupportedFlashModes(call: PluginCall) {
174
+ implementation.getSupportedFlashModes { supportedFlashModes ->
175
+ val modesArray = JSArray().apply {
176
+ supportedFlashModes.forEach { put(it) }
177
+ }
178
+
179
+ call.resolve(JSObject().apply { put("flashModes", modesArray) })
180
+ }
181
+ }
182
+
183
+ @PluginMethod
184
+ fun setFlashMode(call: PluginCall) {
185
+ val mode = call.getString("mode")
186
+ if (mode == null) {
187
+ call.reject("Flash mode must be provided")
188
+ return
189
+ }
190
+
191
+ val validModes = listOf("off", "on", "auto")
192
+ if (!validModes.contains(mode)) {
193
+ call.reject("Invalid flash mode. Must be one of: ${validModes.joinToString(", ")}")
194
+ return
195
+ }
196
+
197
+ try {
198
+ implementation.setFlashMode(mode)
199
+ call.resolve()
200
+ } catch (e: Exception) {
201
+ call.reject("Failed to set flash mode: ${e.localizedMessage}", e)
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Called by the CameraView when a barcode is detected.
207
+ */
208
+ fun notifyBarcodeDetected(result: BarcodeDetectionResult) {
209
+ val jsObject = JSObject().apply {
210
+ put("value", result.value)
211
+ put("type", result.type)
212
+ put("boundingRect", JSObject().apply {
213
+ put("x", result.boundingRect.x)
214
+ put("y", result.boundingRect.y)
215
+ put("width", result.boundingRect.width)
216
+ put("height", result.boundingRect.height)
217
+ })
218
+ }
219
+
220
+ notifyListeners("barcodeDetected", jsObject)
221
+ }
222
+
223
+ override fun handleOnDestroy() {
224
+ implementation.cleanup()
225
+ super.handleOnDestroy()
226
+ }
227
+ }
@@ -0,0 +1,11 @@
1
+ package com.michaelwolz.capacitorcameraview.model
2
+
3
+ /**
4
+ * Barcode detection result containing the value, format type, and normalized bounding rectangle.
5
+ */
6
+ data class BarcodeDetectionResult(
7
+ val value: String,
8
+ val displayValue: String,
9
+ val type: String,
10
+ val boundingRect: WebBoundingRect
11
+ )
@@ -0,0 +1,14 @@
1
+ package com.michaelwolz.capacitorcameraview.model
2
+
3
+ /**
4
+ * Represents a camera device available on the Android device.
5
+ *
6
+ * @property id Unique identifier for the camera
7
+ * @property name Human-readable name of the camera
8
+ * @property position Position of the camera ("front" or "back")
9
+ */
10
+ data class CameraDevice(
11
+ val id: String,
12
+ val name: String,
13
+ val position: String
14
+ )
@@ -0,0 +1,10 @@
1
+ package com.michaelwolz.capacitorcameraview.model
2
+
3
+ /** Configuration for a camera session. */
4
+ data class CameraSessionConfiguration(
5
+ val deviceId: String? = null,
6
+ val enableBarcodeDetection: Boolean = false,
7
+ val position: String = "back",
8
+ val zoomFactor: Float = 1.0f
9
+ )
10
+
@@ -0,0 +1,16 @@
1
+ package com.michaelwolz.capacitorcameraview.model
2
+
3
+ /** Normalized rectangle for barcode bounds. */
4
+ data class WebBoundingRect(
5
+ /** Top left x coordinate of the rectangle. */
6
+ val x: Float,
7
+
8
+ /** Top left y coordinate of the rectangle. */
9
+ val y: Float,
10
+
11
+ /** Width of the rectangle. */
12
+ val width: Float,
13
+
14
+ /** Height of the rectangle. */
15
+ val height: Float
16
+ )
@@ -0,0 +1,14 @@
1
+ package com.michaelwolz.capacitorcameraview.model
2
+
3
+ /**
4
+ * Represents supported zoom factors for the camera.
5
+ *
6
+ * @property min Minimum zoom level (typically 1.0)
7
+ * @property max Maximum zoom level supported by the device
8
+ * @property current Current zoom level in use
9
+ */
10
+ data class ZoomFactors(
11
+ val min: Float,
12
+ val max: Float,
13
+ val current: Float
14
+ )
@@ -0,0 +1,86 @@
1
+ package com.michaelwolz.capacitorcameraview
2
+
3
+ import android.graphics.Rect
4
+ import android.view.View
5
+ import android.view.ViewGroup.MarginLayoutParams
6
+ import androidx.camera.view.PreviewView
7
+ import com.getcapacitor.PluginCall
8
+ import com.google.mlkit.vision.barcode.common.Barcode
9
+ import com.michaelwolz.capacitorcameraview.model.CameraSessionConfiguration
10
+ import com.michaelwolz.capacitorcameraview.model.WebBoundingRect
11
+
12
+ /** Converts a barcode format code to a readable string. */
13
+ fun getBarcodeFormatString(format: Int): String {
14
+ return when (format) {
15
+ Barcode.FORMAT_QR_CODE -> "qr"
16
+ Barcode.FORMAT_AZTEC -> "aztec"
17
+ Barcode.FORMAT_CODABAR -> "codabar"
18
+ Barcode.FORMAT_CODE_39 -> "code39"
19
+ Barcode.FORMAT_CODE_93 -> "code93"
20
+ Barcode.FORMAT_CODE_128 -> "code128"
21
+ Barcode.FORMAT_DATA_MATRIX -> "dataMatrix"
22
+ Barcode.FORMAT_EAN_8 -> "ean8"
23
+ Barcode.FORMAT_EAN_13 -> "ean13"
24
+ Barcode.FORMAT_ITF -> "itf"
25
+ Barcode.FORMAT_PDF417 -> "pdf417"
26
+ Barcode.FORMAT_UPC_A -> "upcA"
27
+ Barcode.FORMAT_UPC_E -> "upcE"
28
+ else -> "unknown"
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Converts the bounding box of a barcode detection result to a [WebBoundingRect]
34
+ * suitable for use in the web view via regular CSS pixels.
35
+ *
36
+ * @param previewView The [PreviewView] used for the camera preview.
37
+ * @param boundingBox The bounding box of the barcode detection result.
38
+ * @param topOffset The top offset to be subtracted from the bounding box's top coordinate.
39
+ */
40
+ fun boundingBoxToWebBoundingRect(
41
+ previewView: PreviewView,
42
+ boundingBox: Rect?,
43
+ topOffset: Int = 0
44
+ ): WebBoundingRect {
45
+ if (boundingBox == null) {
46
+ return WebBoundingRect(0f, 0f, 0f, 0f)
47
+ }
48
+
49
+ val devicePixelRatio = previewView.context.resources.displayMetrics.density
50
+
51
+ return WebBoundingRect(
52
+ x = boundingBox.left.toFloat() / devicePixelRatio,
53
+ y = (boundingBox.top.toFloat() - topOffset) / devicePixelRatio,
54
+ width = boundingBox.width().toFloat() / devicePixelRatio,
55
+ height = boundingBox.height().toFloat() / devicePixelRatio
56
+ )
57
+ }
58
+
59
+ /**
60
+ * Helper method for calculating the top margin of the capacitor web view for correctly
61
+ * positioning the barcode detection rectangle due to weird android edge-to-edge behavior
62
+ * with web views and capacitors hack around this:
63
+ * https://github.com/ionic-team/capacitor/pull/7871
64
+ *
65
+ * Not subtracting the margins will lead to the barcode detection rectangle being
66
+ * positioned incorrectly too low on the screen.
67
+ */
68
+ fun calculateTopOffset(webView: View): Int {
69
+ val layoutParams = webView.layoutParams
70
+
71
+ if (layoutParams is MarginLayoutParams) {
72
+ return layoutParams.topMargin
73
+ }
74
+
75
+ return 0
76
+ }
77
+
78
+ /** Maps a Capacitor plugin call to a [CameraSessionConfiguration]. */
79
+ fun sessionConfigFromPluginCall(call: PluginCall): CameraSessionConfiguration {
80
+ return CameraSessionConfiguration(
81
+ deviceId = call.getString("deviceId"),
82
+ enableBarcodeDetection = call.getBoolean("enableBarcodeDetection") ?: false,
83
+ position = call.getString("position") ?: "back",
84
+ zoomFactor = call.getFloat("zoomFactor") ?: 1.0f
85
+ )
86
+ }
File without changes