Skip to content

Commit 946f09e

Browse files
committed
feat(*): add torch support
1 parent c5347b9 commit 946f09e

File tree

18 files changed

+467
-16
lines changed

18 files changed

+467
-16
lines changed

README.md

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
- 📸 Capture photos or frames from the camera preview.
3232
- 🔍 **Barcode detection** support.
3333
- 📱 **Virtual device support** for automatic lens selection based on zoom level and focus (iOS only).
34-
- 🔎 Control **zoom** and **flash** modes programmatically.
34+
- 🔦 Control **zoom**, **flash** and **torch** modes programmatically.
3535
-**High performance** with optimized native implementations.
3636
- 🎯 **Simple to use** with a clean and intuitive API.
3737
- 🌐 Works seamlessly on **iOS**, **Android**, and **Web**.
@@ -210,6 +210,9 @@ chore: update dependencies
210210
* [`getFlashMode()`](#getflashmode)
211211
* [`getSupportedFlashModes()`](#getsupportedflashmodes)
212212
* [`setFlashMode(...)`](#setflashmode)
213+
* [`isTorchAvailable()`](#istorchavailable)
214+
* [`getTorchMode()`](#gettorchmode)
215+
* [`setTorchMode(...)`](#settorchmode)
213216
* [`checkPermissions()`](#checkpermissions)
214217
* [`requestPermissions()`](#requestpermissions)
215218
* [`addListener('barcodeDetected', ...)`](#addlistenerbarcodedetected-)
@@ -422,6 +425,53 @@ Set the camera flash mode.
422425
--------------------
423426

424427

428+
### isTorchAvailable()
429+
430+
```typescript
431+
isTorchAvailable() => Promise<IsTorchAvailableResponse>
432+
```
433+
434+
Check if the device supports torch (flashlight) functionality.
435+
436+
**Returns:** <code>Promise&lt;<a href="#istorchavailableresponse">IsTorchAvailableResponse</a>&gt;</code>
437+
438+
**Since:** 1.2.0
439+
440+
--------------------
441+
442+
443+
### getTorchMode()
444+
445+
```typescript
446+
getTorchMode() => Promise<GetTorchModeResponse>
447+
```
448+
449+
Get the current torch (flashlight) state.
450+
451+
**Returns:** <code>Promise&lt;<a href="#gettorchmoderesponse">GetTorchModeResponse</a>&gt;</code>
452+
453+
**Since:** 1.2.0
454+
455+
--------------------
456+
457+
458+
### setTorchMode(...)
459+
460+
```typescript
461+
setTorchMode(options: { enabled: boolean; level?: number; }) => Promise<void>
462+
```
463+
464+
Set the torch (flashlight) mode and intensity.
465+
466+
| Param | Type | Description |
467+
| ------------- | -------------------------------------------------- | ----------------------------- |
468+
| **`options`** | <code>{ enabled: boolean; level?: number; }</code> | - Torch configuration options |
469+
470+
**Since:** 1.2.0
471+
472+
--------------------
473+
474+
425475
### checkPermissions()
426476

427477
```typescript
@@ -577,6 +627,25 @@ Response for getting supported flash modes.
577627
| **`flashModes`** | <code>FlashMode[]</code> | An array of flash modes supported by the current camera |
578628

579629

630+
#### IsTorchAvailableResponse
631+
632+
Response for checking torch availability.
633+
634+
| Prop | Type | Description |
635+
| --------------- | -------------------- | ----------------------------------------------------------------- |
636+
| **`available`** | <code>boolean</code> | Indicates if the device supports torch (flashlight) functionality |
637+
638+
639+
#### GetTorchModeResponse
640+
641+
Response for getting the current torch mode.
642+
643+
| Prop | Type | Description |
644+
| ------------- | -------------------- | -------------------------------------------------------------------------------------------- |
645+
| **`enabled`** | <code>boolean</code> | Indicates if the torch is currently enabled |
646+
| **`level`** | <code>number</code> | The current torch intensity level (0.0 to 1.0, iOS only). Always 1.0 on Android when enabled |
647+
648+
580649
#### PermissionStatus
581650

582651
Response for the camera permission status.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.michaelwolz.capacitorcameraview
2+
3+
sealed class CameraError(message: String) : Exception(message) {
4+
class TorchUnavailable : CameraError("Torch is not available on this device")
5+
class CameraNotInitialized : CameraError("Camera controller not initialized")
6+
class PreviewNotInitialized : CameraError("Camera preview not initialized")
7+
class LifecycleOwnerMissing : CameraError("WebView context must be a LifecycleOwner")
8+
class ZoomFactorOutOfRange : CameraError("The requested zoom factor is out of range.")
9+
}

android/src/main/java/com/michaelwolz/capacitorcameraview/CameraView.kt

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class CameraView(plugin: Plugin) {
5555
// Camera state
5656
private var currentCameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
5757
private var currentFlashMode: Int = ImageCapture.FLASH_MODE_OFF
58+
private var isTorchEnabled: Boolean = false
5859

5960
// Plugin context
6061
private var lifecycleOwner: LifecycleOwner? = null
@@ -71,7 +72,7 @@ class CameraView(plugin: Plugin) {
7172
val lifecycleOwner =
7273
context as? LifecycleOwner
7374
?: run {
74-
callback(Exception("WebView context must be a LifecycleOwner"))
75+
callback(CameraError.LifecycleOwnerMissing())
7576
return
7677
}
7778

@@ -132,13 +133,13 @@ class CameraView(plugin: Plugin) {
132133
val controller =
133134
this.cameraController
134135
?: run {
135-
callback(null, Exception("Camera controller not initialized"))
136+
callback(null, CameraError.CameraNotInitialized())
136137
return
137138
}
138139

139140
val preview = previewView
140141
?: run {
141-
callback(null, Exception("Camera preview not initialized"))
142+
callback(null, CameraError.PreviewNotInitialized())
142143
return
143144
}
144145

@@ -250,7 +251,7 @@ class CameraView(plugin: Plugin) {
250251
val previewView =
251252
this.previewView
252253
?: run {
253-
callback(null, Exception("Camera preview not initialized"))
254+
callback(null, CameraError.PreviewNotInitialized())
254255
return
255256
}
256257

@@ -309,7 +310,7 @@ class CameraView(plugin: Plugin) {
309310
val controller =
310311
this.cameraController
311312
?: run {
312-
callback(Exception("Camera controller not initialized"))
313+
callback(CameraError.CameraNotInitialized())
313314
return
314315
}
315316

@@ -327,14 +328,14 @@ class CameraView(plugin: Plugin) {
327328
val cameraControl =
328329
cameraController?.cameraControl
329330
?: run {
330-
callback?.invoke(Exception("Camera controller not initialized"))
331+
callback?.invoke(CameraError.CameraNotInitialized())
331332
return@post
332333
}
333334

334335
val availableZoomFactors = getZoomFactorsInternal()
335336

336337
if (zoomFactor !in availableZoomFactors.min..availableZoomFactors.max) {
337-
callback?.invoke(Exception("The requested zoom factor is out of range."))
338+
callback?.invoke(CameraError.ZoomFactorOutOfRange())
338339
return@post
339340
}
340341

@@ -402,6 +403,33 @@ class CameraView(plugin: Plugin) {
402403
mainHandler.post { controller.imageCaptureFlashMode = currentFlashMode }
403404
}
404405

406+
/** Check if torch is available */
407+
fun isTorchAvailable(): Boolean {
408+
val cameraInfo = cameraController?.cameraInfo ?: return false
409+
return cameraInfo.hasFlashUnit()
410+
}
411+
412+
/** Get the current torch mode */
413+
fun getTorchMode(): Boolean {
414+
return isTorchEnabled
415+
}
416+
417+
/** Set the torch mode */
418+
fun setTorchMode(enabled: Boolean) {
419+
val controller = this.cameraController
420+
?: run { throw CameraError.CameraNotInitialized() }
421+
422+
val cameraInfo = controller.cameraInfo
423+
if (cameraInfo?.hasFlashUnit() != true) {
424+
throw CameraError.TorchUnavailable()
425+
}
426+
427+
isTorchEnabled = enabled
428+
mainHandler.post {
429+
controller.cameraControl?.enableTorch(enabled)
430+
}
431+
}
432+
405433
/** Get a list of available camera devices */
406434
fun getAvailableDevices(): List<CameraDevice> {
407435
try {

android/src/main/java/com/michaelwolz/capacitorcameraview/CameraViewPlugin.kt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,51 @@ class CameraViewPlugin : Plugin() {
209209
}
210210
}
211211

212+
@PluginMethod
213+
fun isTorchAvailable(call: PluginCall) {
214+
try {
215+
val available = implementation.isTorchAvailable()
216+
call.resolve(JSObject().apply {
217+
put("available", available)
218+
})
219+
} catch (e: Exception) {
220+
call.reject("Failed to check torch availability: ${e.localizedMessage}", e)
221+
}
222+
}
223+
224+
@PluginMethod
225+
fun getTorchMode(call: PluginCall) {
226+
try {
227+
val enabled = implementation.getTorchMode()
228+
call.resolve(JSObject().apply {
229+
put("enabled", enabled)
230+
put(
231+
"level",
232+
// Android always uses full intensity when enabled
233+
if (enabled) 1.0f else 0.0f
234+
)
235+
})
236+
} catch (e: Exception) {
237+
call.reject("Failed to get torch mode: ${e.localizedMessage}", e)
238+
}
239+
}
240+
241+
@PluginMethod
242+
fun setTorchMode(call: PluginCall) {
243+
val enabled = call.getBoolean("enabled")
244+
if (enabled == null) {
245+
call.reject("Enabled parameter is required")
246+
return
247+
}
248+
249+
try {
250+
implementation.setTorchMode(enabled)
251+
call.resolve()
252+
} catch (e: Exception) {
253+
call.reject("Failed to set torch mode: ${e.localizedMessage}", e)
254+
}
255+
}
256+
212257
/**
213258
* Called by the CameraView when a barcode is detected.
214259
*/
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
11
{
2-
"recommendations": [
3-
"ionic.ionic"
4-
]
2+
"recommendations": ["ionic.ionic"]
53
}

example-app/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"build": "ng build",
1010
"watch": "ng build --watch --configuration development",
1111
"test": "ng test",
12-
"lint": "ng lint"
12+
"lint": "ng lint",
13+
"format": "prettier --write \"src/**/*.{ts,js,html,scss,css,json}\""
1314
},
1415
"private": true,
1516
"dependencies": {

example-app/src/app/app.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
closeCircle,
1212
flash,
1313
flashOff,
14+
flashlightOutline,
1415
imagesSharp,
1516
qrCodeOutline,
1617
remove,
@@ -38,6 +39,7 @@ export class AppComponent {
3839
closeCircle,
3940
flash,
4041
flashOff,
42+
flashlightOutline,
4143
imagesSharp,
4244
qrCodeOutline,
4345
remove,

example-app/src/app/components/camera-modal/camera-modal.component.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,23 @@
4040
}
4141
</ion-fab-button>
4242

43+
<ion-fab-button
44+
(click)="toggleTorch()"
45+
size="small"
46+
[disabled]="!torchAvailable()"
47+
>
48+
@if (torchEnabled() && torchAvailable()) {
49+
<div style="line-height: 0.5; margin-top: -5px">
50+
<ion-icon name="flashlight-outline" size="small"></ion-icon><br />
51+
<span style="font-size: 0.5rem"
52+
>{{ (torchLevel() * 100).toFixed(0) }}%</span
53+
>
54+
</div>
55+
} @else {
56+
<ion-icon name="flashlight-outline" size="small"></ion-icon>
57+
}
58+
</ion-fab-button>
59+
4360
<ion-fab-button size="small" (click)="flipCamera()">
4461
<ion-icon name="camera-reverse"></ion-icon>
4562
</ion-fab-button>

example-app/src/app/components/camera-modal/camera-modal.component.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,4 +100,4 @@ ion-chip.barcode-detected {
100100
opacity: 1;
101101
transform: translate(-50%, 0);
102102
}
103-
}
103+
}

0 commit comments

Comments
 (0)