You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
155 lines
3.9 KiB
155 lines
3.9 KiB
import {
|
|
BarcodeScanner,
|
|
BarcodeFormat,
|
|
LensFacing,
|
|
ScanResult,
|
|
} from "@capacitor-mlkit/barcode-scanning";
|
|
import type { QRScannerService, ScanListener } from "./types";
|
|
import { logger } from "../../utils/logger";
|
|
|
|
export class CapacitorQRScanner implements QRScannerService {
|
|
private scanListener: ScanListener | null = null;
|
|
private isScanning = false;
|
|
private listenerHandles: Array<() => Promise<void>> = [];
|
|
|
|
async checkPermissions() {
|
|
try {
|
|
const { camera } = await BarcodeScanner.checkPermissions();
|
|
return camera === "granted";
|
|
} catch (error) {
|
|
logger.error("Error checking camera permissions:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async requestPermissions() {
|
|
try {
|
|
const { camera } = await BarcodeScanner.requestPermissions();
|
|
return camera === "granted";
|
|
} catch (error) {
|
|
logger.error("Error requesting camera permissions:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async isSupported() {
|
|
try {
|
|
const { supported } = await BarcodeScanner.isSupported();
|
|
return supported;
|
|
} catch (error) {
|
|
logger.error("Error checking barcode scanner support:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async startScan() {
|
|
if (this.isScanning) {
|
|
logger.warn("Scanner is already active");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// First register listeners before starting scan
|
|
await this.registerListeners();
|
|
|
|
this.isScanning = true;
|
|
await BarcodeScanner.startScan({
|
|
formats: [BarcodeFormat.QrCode],
|
|
lensFacing: LensFacing.Back,
|
|
});
|
|
} catch (error) {
|
|
// Ensure cleanup on error
|
|
this.isScanning = false;
|
|
await this.removeListeners();
|
|
logger.error("Error starting barcode scan:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async stopScan() {
|
|
if (!this.isScanning) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// First stop the scan
|
|
await BarcodeScanner.stopScan();
|
|
} catch (error) {
|
|
logger.error("Error stopping barcode scan:", error);
|
|
} finally {
|
|
// Always cleanup state even if stop fails
|
|
this.isScanning = false;
|
|
await this.removeListeners();
|
|
}
|
|
}
|
|
|
|
private async registerListeners() {
|
|
// Clear any existing listeners first
|
|
await this.removeListeners();
|
|
|
|
const scanHandle = await BarcodeScanner.addListener(
|
|
"barcodesScanned",
|
|
(result: ScanResult) => {
|
|
if (result.barcodes.length > 0) {
|
|
const barcode = result.barcodes[0];
|
|
if (barcode.rawValue && this.scanListener) {
|
|
this.scanListener.onScan(barcode.rawValue);
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
const errorHandle = await BarcodeScanner.addListener(
|
|
"scanError",
|
|
(error) => {
|
|
logger.error("Scan error:", error);
|
|
if (this.scanListener?.onError) {
|
|
this.scanListener.onError(
|
|
new Error(error.message || "Unknown scan error"),
|
|
);
|
|
}
|
|
},
|
|
);
|
|
|
|
this.listenerHandles.push(
|
|
async () => await scanHandle.remove(),
|
|
async () => await errorHandle.remove(),
|
|
);
|
|
}
|
|
|
|
private async removeListeners() {
|
|
try {
|
|
// Remove all registered listener handles
|
|
await Promise.all(this.listenerHandles.map((handle) => handle()));
|
|
this.listenerHandles = [];
|
|
} catch (error) {
|
|
logger.error("Error removing listeners:", error);
|
|
}
|
|
}
|
|
|
|
addListener(listener: ScanListener) {
|
|
if (this.scanListener) {
|
|
logger.warn("Scanner listener already exists, removing old listener");
|
|
this.cleanup();
|
|
}
|
|
this.scanListener = listener;
|
|
}
|
|
|
|
async cleanup() {
|
|
try {
|
|
// Stop scan first if active
|
|
if (this.isScanning) {
|
|
await this.stopScan();
|
|
}
|
|
|
|
// Remove listeners
|
|
await this.removeListeners();
|
|
|
|
// Clear state
|
|
this.scanListener = null;
|
|
this.isScanning = false;
|
|
} catch (error) {
|
|
logger.error("Error during cleanup:", error);
|
|
}
|
|
}
|
|
}
|
|
|