diff --git a/src/Components/QrScanner/QrScannerPlugin.tsx b/src/Components/QrScanner/QrScannerPlugin.tsx index d676179..49ddc56 100644 --- a/src/Components/QrScanner/QrScannerPlugin.tsx +++ b/src/Components/QrScanner/QrScannerPlugin.tsx @@ -1,8 +1,10 @@ // file = QrScannerPlugin.jsx -import {MutableRefObject, useEffect, useRef} from 'react'; +import {MutableRefObject, useEffect, useRef, useState, useCallback} from 'react'; import {ArrowUpTrayIcon} from '@heroicons/react/24/solid'; import {Html5Qrcode, Html5QrcodeScannerState, Html5QrcodeSupportedFormats} from 'html5-qrcode'; +import {useLogError} from '../../hooks/useError'; import {checkCameraPermissions} from '../../utils/media'; +import {TorchButton} from './TorchButton'; import classes from './QrScanner.module.css'; // Id of the HTML element used by the Html5QrcodeScanner. @@ -51,12 +53,32 @@ export default function QrScannerPlugin({ }: QrProps) { const aspectRatio = calcAspectRatio(); const html5CustomScanner: MutableRefObject = useRef(null); + const [canUseCamera, setCanUseCamera] = useState(true); + const logError = useLogError(); + + // Turn off the torch (if it is on) when navigating away from the scan page + const switchOffTorch = useCallback( + async function switchOffTorch(html5CustomScanner: MutableRefObject) { + try { + const track = html5CustomScanner?.current?.getRunningTrackCameraCapabilities(); + if (track && track.torchFeature().value()) { + await track.torchFeature().apply(false); + } + } catch (error) { + // This raises an error about invalid tracks - we have to catch it! (blame the library) + console.warn('Failed to disable torch:', error); + logError(`Failed to disable torch: ${error}`); + } + }, + [logError] + ); useEffect(() => { const showQRCode = async () => { const hasCamPerm: boolean = await checkCameraPermissions(); if (!hasCamPerm) { onPermRefused(); + setCanUseCamera(false); return; } @@ -82,6 +104,7 @@ export default function QrScannerPlugin({ return () => { const stopQrScanner = async () => { + await switchOffTorch(html5CustomScanner); if (html5CustomScanner.current?.isScanning) { await html5CustomScanner.current.stop(); } @@ -101,13 +124,17 @@ export default function QrScannerPlugin({ qrCodeSuccessCallback, qrCodeErrorCallback, onPermRefused, + switchOffTorch, ]); return ( -
- -
-
+ <> +
+ +
+
+ + ); } diff --git a/src/Components/QrScanner/TorchButton.tsx b/src/Components/QrScanner/TorchButton.tsx new file mode 100644 index 0000000..e0bd7ca --- /dev/null +++ b/src/Components/QrScanner/TorchButton.tsx @@ -0,0 +1,77 @@ +import {MutableRefObject, useState, useEffect} from 'react'; +import {BoltIcon, BoltSlashIcon, ExclamationCircleIcon} from '@heroicons/react/24/solid'; +import {Html5Qrcode} from 'html5-qrcode'; +import PropTypes from 'prop-types'; +import {useLogError} from '../../hooks/useError'; + +interface TorchButtonProps { + html5CustomScanner: MutableRefObject; + canUseCamera: boolean; +} + +export function TorchButton({html5CustomScanner, canUseCamera}: TorchButtonProps) { + const [torchOn, setTorchOn] = useState(false); + const [torchUnavailable, setTorchUnavailable] = useState(false); + const logError = useLogError(); + + useEffect(() => { + const toggleTorch = async () => { + try { + const track = html5CustomScanner?.current?.getRunningTrackCameraCapabilities(); + if (track && track.torchFeature().isSupported()) { + await track.torchFeature().apply(torchOn); + } else if (track && !track.torchFeature().isSupported()) { + setTorchUnavailable(true); + console.warn('Torch feature is not supported on this device.'); + } + } catch (error) { + setTorchUnavailable(true); + console.warn('Failed to toggle torch:', error); + logError(`Failed to toggle torch: ${error}`); + } + }; + + toggleTorch(); + }, [torchOn, html5CustomScanner, logError]); + + if (!canUseCamera) { + return; + } + + if (torchUnavailable) { + return ( +
+ + + Your device's torch is unavailable + +
+ ); + } + + return ( +
setTorchOn(prev => !prev)} + className="fit-content flex justify-center gap-1 bg-primary py-3 text-center text-white active:bg-blue-800 dark:bg-blue-600 dark:active:bg-blue-700" + > + + {torchOn ? ( + <> + + Turn torch off + + ) : ( + <> + + Turn torch on + + )} + +
+ ); +} + +TorchButton.propTypes = { + html5CustomScanner: PropTypes.object.isRequired, + canUseCamera: PropTypes.bool.isRequired, +};