fix(qr): improve QR scanner implementation and error handling

- Implement robust QR scanner factory with platform detection
- Add proper camera permissions to Android manifest
- Improve error handling and logging across scanner implementations
- Add continuous scanning mode for Capacitor/MLKit scanner
- Enhance UI feedback during scanning process
- Fix build configuration for proper platform detection
- Clean up resources properly in scanner components
- Add TypeScript improvements and error wrapping

The changes include:
- Adding CAMERA permission to AndroidManifest.xml
- Setting proper build flags (__IS_MOBILE__, __USE_QR_READER__)
- Implementing continuous scanning mode for better UX
- Adding proper cleanup of scanner resources
- Improving error handling and type safety
- Enhancing UI with loading states and error messages
This commit is contained in:
Matthew Raymer
2025-04-22 10:00:37 +00:00
parent 3e87b2ecda
commit 70dfbe44fa
10 changed files with 548 additions and 451 deletions

View File

@@ -8,6 +8,7 @@ export class WebDialogQRScanner implements QRScannerService {
private dialogComponent: InstanceType<typeof QRScannerDialog> | null = null;
private scanListener: ScanListener | null = null;
private isScanning = false;
private container: HTMLElement | null = null;
constructor(private options?: QRScannerOptions) {}
@@ -18,7 +19,9 @@ export class WebDialogQRScanner implements QRScannerService {
});
return permissions.state === "granted";
} catch (error) {
logger.error("Error checking camera permissions:", error);
const wrappedError =
error instanceof Error ? error : new Error(String(error));
logger.error("Error checking camera permissions:", wrappedError);
return false;
}
}
@@ -29,7 +32,9 @@ export class WebDialogQRScanner implements QRScannerService {
stream.getTracks().forEach((track) => track.stop());
return true;
} catch (error) {
logger.error("Error requesting camera permissions:", error);
const wrappedError =
error instanceof Error ? error : new Error(String(error));
logger.error("Error requesting camera permissions:", wrappedError);
return false;
}
}
@@ -47,8 +52,8 @@ export class WebDialogQRScanner implements QRScannerService {
this.isScanning = true;
// Create and mount dialog component
const container = document.createElement("div");
document.body.appendChild(container);
this.container = document.createElement("div");
document.body.appendChild(this.container);
this.dialogInstance = createApp(QRScannerDialog, {
onScan: (result: string) => {
@@ -64,16 +69,18 @@ export class WebDialogQRScanner implements QRScannerService {
options: this.options,
});
this.dialogComponent = this.dialogInstance.mount(container).$refs
this.dialogComponent = this.dialogInstance.mount(this.container).$refs
.dialog as InstanceType<typeof QRScannerDialog>;
} catch (error) {
this.isScanning = false;
const wrappedError =
error instanceof Error ? error : new Error(String(error));
if (this.scanListener?.onError) {
this.scanListener.onError(
error instanceof Error ? error : new Error(String(error)),
);
this.scanListener.onError(wrappedError);
}
logger.error("Error starting scan:", error);
logger.error("Error starting scan:", wrappedError);
this.cleanupContainer();
throw wrappedError;
}
}
@@ -89,9 +96,14 @@ export class WebDialogQRScanner implements QRScannerService {
if (this.dialogInstance) {
this.dialogInstance.unmount();
}
this.isScanning = false;
} catch (error) {
logger.error("Error stopping scan:", error);
const wrappedError =
error instanceof Error ? error : new Error(String(error));
logger.error("Error stopping scan:", wrappedError);
throw wrappedError;
} finally {
this.isScanning = false;
this.cleanupContainer();
}
}
@@ -99,10 +111,26 @@ export class WebDialogQRScanner implements QRScannerService {
this.scanListener = listener;
}
private cleanupContainer(): void {
if (this.container && this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
this.container = null;
}
async cleanup(): Promise<void> {
await this.stopScan();
this.dialogComponent = null;
this.dialogInstance = null;
this.scanListener = null;
try {
await this.stopScan();
} catch (error) {
const wrappedError =
error instanceof Error ? error : new Error(String(error));
logger.error("Error during cleanup:", wrappedError);
throw wrappedError;
} finally {
this.dialogComponent = null;
this.dialogInstance = null;
this.scanListener = null;
this.cleanupContainer();
}
}
}