--- description: globs: alwaysApply: true --- # QR Code Implementation Guide ## Directory Structure ``` src/ ├── components/ │ └── QRScanner/ │ ├── types.ts │ ├── factory.ts │ ├── CapacitorScanner.ts │ ├── WebDialogScanner.ts │ └── QRScannerDialog.vue ├── services/ │ └── QRScanner/ │ ├── types.ts │ ├── QRScannerFactory.ts │ ├── CapacitorQRScanner.ts │ └── WebDialogQRScanner.ts ``` ## Core Interfaces ```typescript // types.ts export interface ScanListener { onScan: (result: string) => void; onError?: (error: Error) => void; } export interface QRScannerService { checkPermissions(): Promise; requestPermissions(): Promise; isSupported(): Promise; startScan(): Promise; stopScan(): Promise; addListener(listener: ScanListener): void; cleanup(): Promise; } ``` ## Configuration Files ### Vite Configuration ```typescript // vite.config.ts export default defineConfig({ define: { __USE_QR_READER__: JSON.stringify(!isMobile), __IS_MOBILE__: JSON.stringify(isMobile), }, build: { rollupOptions: { external: isMobile ? ['vue-qrcode-reader'] : [], } } }); ``` ### Capacitor Configuration ```typescript // capacitor.config.ts const config: CapacitorConfig = { plugins: { MLKitBarcodeScanner: { formats: ['QR_CODE'], detectorSize: 1.0, lensFacing: 'back', googleBarcodeScannerModuleInstallState: true } } }; ``` ## Implementation Steps 1. **Install Dependencies** ```bash npm install @capacitor-mlkit/barcode-scanning vue-qrcode-reader ``` 2. **Create Core Types** Create the interface files as shown above. 3. **Implement Factory** ```typescript // QRScannerFactory.ts export class QRScannerFactory { private static instance: QRScannerService | null = null; static getInstance(): QRScannerService { if (!this.instance) { if (__IS_MOBILE__ || Capacitor.isNativePlatform()) { this.instance = new CapacitorQRScanner(); } else if (__USE_QR_READER__) { this.instance = new WebDialogQRScanner(); } else { throw new Error('No QR scanner implementation available'); } } return this.instance; } static async cleanup() { if (this.instance) { await this.instance.cleanup(); this.instance = null; } } } ``` 4. **Implement Mobile Scanner** ```typescript // CapacitorQRScanner.ts export class CapacitorQRScanner implements QRScannerService { private scanListener: ScanListener | null = null; private isScanning = false; private listenerHandles: Array<() => Promise> = []; async checkPermissions() { try { const { camera } = await BarcodeScanner.checkPermissions(); return camera === 'granted'; } catch (error) { logger.error('Error checking camera permissions:', error); return false; } } // Implement other interface methods... } ``` 5. **Implement Web Scanner** ```typescript // WebDialogQRScanner.ts export class WebDialogQRScanner implements QRScannerService { private dialogInstance: App | null = null; private dialogComponent: InstanceType | null = null; private scanListener: ScanListener | null = null; async checkPermissions(): Promise { try { const permissions = await navigator.permissions.query({ name: 'camera' as PermissionName }); return permissions.state === 'granted'; } catch (error) { logger.error('Error checking camera permissions:', error); return false; } } // Implement other interface methods... } ``` 6. **Create Dialog Component** ```vue ``` ## Usage Example ```typescript // In your component async function scanQRCode() { const scanner = QRScannerFactory.getInstance(); if (!(await scanner.checkPermissions())) { const granted = await scanner.requestPermissions(); if (!granted) { throw new Error('Camera permission denied'); } } scanner.addListener({ onScan: (result) => { console.log('Scanned:', result); }, onError: (error) => { console.error('Scan error:', error); } }); await scanner.startScan(); } // Cleanup when done onUnmounted(() => { QRScannerFactory.cleanup(); }); ``` ## Platform-Specific Notes ### Mobile (Capacitor) - Uses MLKit for optimal performance - Handles native permissions - Supports both iOS and Android - Uses back camera by default - Handles device rotation ### Web - Uses MediaDevices API - Requires HTTPS for camera access - Handles browser compatibility - Manages memory and resources - Provides fallback UI ## Testing 1. **Unit Tests** - Test factory pattern - Test platform detection - Test error handling - Test cleanup procedures 2. **Integration Tests** - Test permission flows - Test camera access - Test QR code detection - Test cross-platform behavior 3. **E2E Tests** - Test full scanning workflow - Test UI feedback - Test error scenarios - Test platform differences ## Common Issues and Solutions 1. **Permission Handling** - Always check permissions first - Provide clear user feedback - Handle denial gracefully - Implement retry logic 2. **Resource Management** - Clean up after scanning - Handle component unmounting - Release camera resources - Clear event listeners 3. **Error Handling** - Log errors appropriately - Provide user feedback - Implement fallbacks - Handle edge cases 4. **Performance** - Optimize camera preview - Handle memory usage - Manage battery impact - Consider device capabilities # QR Code Implementation Guide ## Directory Structure ``` src/ ├── components/ │ └── QRScanner/ │ ├── types.ts │ ├── factory.ts │ ├── CapacitorScanner.ts │ ├── WebDialogScanner.ts │ └── QRScannerDialog.vue ├── services/ │ └── QRScanner/ │ ├── types.ts │ ├── QRScannerFactory.ts │ ├── CapacitorQRScanner.ts │ └── WebDialogQRScanner.ts ``` ## Core Interfaces ```typescript // types.ts export interface ScanListener { onScan: (result: string) => void; onError?: (error: Error) => void; } export interface QRScannerService { checkPermissions(): Promise; requestPermissions(): Promise; isSupported(): Promise; startScan(): Promise; stopScan(): Promise; addListener(listener: ScanListener): void; cleanup(): Promise; } ``` ## Configuration Files ### Vite Configuration ```typescript // vite.config.ts export default defineConfig({ define: { __USE_QR_READER__: JSON.stringify(!isMobile), __IS_MOBILE__: JSON.stringify(isMobile), }, build: { rollupOptions: { external: isMobile ? ['vue-qrcode-reader'] : [], } } }); ``` ### Capacitor Configuration ```typescript // capacitor.config.ts const config: CapacitorConfig = { plugins: { MLKitBarcodeScanner: { formats: ['QR_CODE'], detectorSize: 1.0, lensFacing: 'back', googleBarcodeScannerModuleInstallState: true } } }; ``` ## Implementation Steps 1. **Install Dependencies** ```bash npm install @capacitor-mlkit/barcode-scanning vue-qrcode-reader ``` 2. **Create Core Types** Create the interface files as shown above. 3. **Implement Factory** ```typescript // QRScannerFactory.ts export class QRScannerFactory { private static instance: QRScannerService | null = null; static getInstance(): QRScannerService { if (!this.instance) { if (__IS_MOBILE__ || Capacitor.isNativePlatform()) { this.instance = new CapacitorQRScanner(); } else if (__USE_QR_READER__) { this.instance = new WebDialogQRScanner(); } else { throw new Error('No QR scanner implementation available'); } } return this.instance; } static async cleanup() { if (this.instance) { await this.instance.cleanup(); this.instance = null; } } } ``` 4. **Implement Mobile Scanner** ```typescript // CapacitorQRScanner.ts export class CapacitorQRScanner implements QRScannerService { private scanListener: ScanListener | null = null; private isScanning = false; private listenerHandles: Array<() => Promise> = []; async checkPermissions() { try { const { camera } = await BarcodeScanner.checkPermissions(); return camera === 'granted'; } catch (error) { logger.error('Error checking camera permissions:', error); return false; } } // Implement other interface methods... } ``` 5. **Implement Web Scanner** ```typescript // WebDialogQRScanner.ts export class WebDialogQRScanner implements QRScannerService { private dialogInstance: App | null = null; private dialogComponent: InstanceType | null = null; private scanListener: ScanListener | null = null; async checkPermissions(): Promise { try { const permissions = await navigator.permissions.query({ name: 'camera' as PermissionName }); return permissions.state === 'granted'; } catch (error) { logger.error('Error checking camera permissions:', error); return false; } } // Implement other interface methods... } ``` 6. **Create Dialog Component** ```vue ``` ## Usage Example ```typescript // In your component async function scanQRCode() { const scanner = QRScannerFactory.getInstance(); if (!(await scanner.checkPermissions())) { const granted = await scanner.requestPermissions(); if (!granted) { throw new Error('Camera permission denied'); } } scanner.addListener({ onScan: (result) => { console.log('Scanned:', result); }, onError: (error) => { console.error('Scan error:', error); } }); await scanner.startScan(); } // Cleanup when done onUnmounted(() => { QRScannerFactory.cleanup(); }); ``` ## Platform-Specific Notes ### Mobile (Capacitor) - Uses MLKit for optimal performance - Handles native permissions - Supports both iOS and Android - Uses back camera by default - Handles device rotation ### Web - Uses MediaDevices API - Requires HTTPS for camera access - Handles browser compatibility - Manages memory and resources - Provides fallback UI ## Testing 1. **Unit Tests** - Test factory pattern - Test platform detection - Test error handling - Test cleanup procedures 2. **Integration Tests** - Test permission flows - Test camera access - Test QR code detection - Test cross-platform behavior 3. **E2E Tests** - Test full scanning workflow - Test UI feedback - Test error scenarios - Test platform differences ## Common Issues and Solutions 1. **Permission Handling** - Always check permissions first - Provide clear user feedback - Handle denial gracefully - Implement retry logic 2. **Resource Management** - Clean up after scanning - Handle component unmounting - Release camera resources - Clear event listeners 3. **Error Handling** - Log errors appropriately - Provide user feedback - Implement fallbacks - Handle edge cases 4. **Performance** - Optimize camera preview - Handle memory usage - Manage battery impact - Consider device capabilities