21 KiB
						
					
					
				
			
		
		
		
			
			
			
				
					
				
				
					
				
			
		
		
	
	QR Code Implementation Guide
Overview
This document describes the QR code scanning and generation implementation in the TimeSafari application. The system uses a platform-agnostic design with specific implementations for web and mobile platforms.
Architecture
Directory Structure
src/
├── services/
│   └── QRScanner/
│       ├── types.ts              # Core interfaces and types
│       ├── QRScannerFactory.ts   # Factory for creating scanner instances
│       ├── CapacitorQRScanner.ts # Mobile implementation using MLKit
│       ├── WebInlineQRScanner.ts # Web implementation using MediaDevices API
│       └── interfaces.ts         # Additional interfaces
├── components/
│   └── QRScanner/
│       └── QRScannerDialog.vue   # Shared UI component
└── views/
    ├── ContactQRScanView.vue     # Dedicated scanning view
    └── ContactQRScanShowView.vue # Combined QR display and scanning view
Core Components
- 
Factory Pattern
QRScannerFactory- Creates appropriate scanner instance based on platform- Common interface 
QRScannerServiceimplemented by all scanners - Platform detection via Capacitor and build flags
 
 - 
Platform-Specific Implementations
CapacitorQRScanner- Native mobile implementation using MLKitWebInlineQRScanner- Web browser implementation using MediaDevices APIQRScannerDialog.vue- Shared UI component
 - 
View Components
ContactQRScanView- Dedicated view for scanning QR codesContactQRScanShowView- Combined view for displaying and scanning QR codes
 
Implementation Details
Core Interfaces
interface QRScannerService {
  checkPermissions(): Promise<boolean>;
  requestPermissions(): Promise<boolean>;
  isSupported(): Promise<boolean>;
  startScan(options?: QRScannerOptions): Promise<void>;
  stopScan(): Promise<void>;
  addListener(listener: ScanListener): void;
  onStream(callback: (stream: MediaStream | null) => void): void;
  cleanup(): Promise<void>;
  getAvailableCameras(): Promise<MediaDeviceInfo[]>;
  switchCamera(deviceId: string): Promise<void>;
  getCurrentCamera(): Promise<MediaDeviceInfo | null>;
}
interface ScanListener {
  onScan: (result: string) => void;
  onError?: (error: Error) => void;
}
interface QRScannerOptions {
  camera?: "front" | "back";
  showPreview?: boolean;
  playSound?: boolean;
}
Platform-Specific Implementations
Mobile (Capacitor)
- Uses 
@capacitor-mlkit/barcode-scanning - Native camera access through platform APIs
 - Optimized for mobile performance
 - Supports both iOS and Android
 - Real-time QR code detection
 - Back camera preferred for scanning
 
Configuration:
// capacitor.config.ts
const config: CapacitorConfig = {
  plugins: {
    MLKitBarcodeScanner: {
      formats: ['QR_CODE'],
      detectorSize: 1.0,
      lensFacing: 'back',
      googleBarcodeScannerModuleInstallState: true,
      // Additional camera options
      cameraOptions: {
        quality: 0.8,
        allowEditing: false,
        resultType: 'uri',
        sourceType: 'CAMERA',
        saveToGallery: false
      }
    }
  }
};
Web
- Uses browser's MediaDevices API
 - Vue.js components for UI
 - EventEmitter for stream management
 - Browser-based camera access
 - Inline camera preview
 - Responsive design
 - Cross-browser compatibility
 
View Components
ContactQRScanView
- Dedicated view for scanning QR codes
 - Full-screen camera interface
 - Simple UI focused on scanning
 - Used primarily on native platforms
 - Streamlined scanning experience
 
ContactQRScanShowView
- Combined view for QR code display and scanning
 - Shows user's own QR code
 - Handles user registration status
 - Provides options to copy contact information
 - Platform-specific scanning implementation:
- Native: Button to navigate to ContactQRScanView
 - Web: Built-in scanning functionality
 
 
QR Code Workflow
- 
Initiation
- User selects "Scan QR Code" option
 - Platform-specific scanner is initialized
 - Camera permissions are verified
 - Appropriate scanner component is loaded
 
 - 
Platform-Specific Implementation
- Web: Uses 
qrcode-streamfor real-time scanning - Native: Uses 
@capacitor-mlkit/barcode-scanning 
 - Web: Uses 
 - 
Scanning Process
- Camera stream initialization
 - Real-time frame analysis
 - QR code detection and decoding
 - Validation of QR code format
 - Processing of contact information
 
 - 
Contact Processing
- Decryption of contact data
 - Validation of user information
 - Verification of timestamp
 - Check for duplicate contacts
 - Processing of shared data
 
 
Build Configuration
Common Vite Configuration
// vite.config.common.mts
export async function createBuildConfig(mode: string) {
  const isCapacitor = mode === "capacitor";
  
  return defineConfig({
    define: {
      'process.env.VITE_PLATFORM': JSON.stringify(mode),
      // PWA is automatically enabled for web platforms via build configuration
      __IS_MOBILE__: JSON.stringify(isCapacitor),
      __USE_QR_READER__: JSON.stringify(!isCapacitor)
    },
    optimizeDeps: {
      include: [
        '@capacitor-mlkit/barcode-scanning',
        'vue-qrcode-reader'
      ]
    }
  });
}
Platform-Specific Builds
{
  "scripts": {
    "build:web": "vite build --config vite.config.web.mts",
    "build:capacitor": "vite build --config vite.config.capacitor.mts",
    "build:all": "npm run build:web && npm run build:capacitor"
  }
}
Error Handling
Common Error Scenarios
- No camera found
 - Permission denied
 - Camera in use by another application
 - HTTPS required
 - Browser compatibility issues
 - Invalid QR code format
 - Expired QR codes
 - Duplicate contact attempts
 - Network connectivity issues
 
Error Response
- User-friendly error messages
 - Troubleshooting tips
 - Clear instructions for resolution
 - Platform-specific guidance
 
Security Considerations
QR Code Security
- Encryption of contact data
 - Timestamp validation
 - Version checking
 - User verification
 - Rate limiting for scans
 
Data Protection
- Secure transmission of contact data
 - Validation of QR code authenticity
 - Prevention of duplicate scans
 - Protection against malicious codes
 - Secure storage of contact information
 
Best Practices
Camera Access
- Always check for camera availability
 - Request permissions explicitly
 - Handle all error conditions
 - Provide clear user feedback
 - Implement proper cleanup
 
Performance
- Optimize camera resolution
 - Implement proper resource cleanup
 - Handle camera switching efficiently
 - Manage memory usage
 - Battery usage optimization
 
User Experience
- Clear visual feedback
 - Camera preview
 - Scanning status indicators
 - Error messages
 - Success confirmations
 - Intuitive camera controls
 - Smooth camera switching
 - Responsive UI feedback
 
Testing
Test Scenarios
- Permission handling
 - Camera switching
 - Error conditions
 - Platform compatibility
 - Performance metrics
 - QR code detection
 - Contact processing
 - Security validation
 
Test Environment
- Multiple browsers
 - iOS and Android devices
 - Various network conditions
 - Different camera configurations
 
Dependencies
Key Packages
@capacitor-mlkit/barcode-scanningqrcode-streamvue-qrcode-reader- Platform-specific camera APIs
 
Maintenance
Regular Updates
- Keep dependencies updated
 - Monitor platform changes
 - Update documentation
 - Review security patches
 
Performance Monitoring
- Track memory usage
 - Monitor camera performance
 - Check error rates
 - Analyze user feedback
 
Camera Handling
Camera Switching Implementation
The QR scanner supports camera switching on both mobile and desktop platforms through a unified interface.
Platform-Specific Implementations
- 
Mobile (Capacitor)
- Uses 
@capacitor-mlkit/barcode-scanning - Supports front/back camera switching
 - Native camera access through platform APIs
 - Optimized for mobile performance
 
// CapacitorQRScanner.ts async startScan(options?: QRScannerOptions): Promise<void> { const scanOptions: StartScanOptions = { formats: [BarcodeFormat.QrCode], lensFacing: options?.camera === "front" ? LensFacing.Front : LensFacing.Back }; await BarcodeScanner.startScan(scanOptions); } - Uses 
 - 
Web (Desktop)
- Uses browser's MediaDevices API
 - Supports multiple camera devices
 - Dynamic camera enumeration
 - Real-time camera switching
 
// WebInlineQRScanner.ts async getAvailableCameras(): Promise<MediaDeviceInfo[]> { const devices = await navigator.mediaDevices.enumerateDevices(); return devices.filter(device => device.kind === 'videoinput'); } async switchCamera(deviceId: string): Promise<void> { // Stop current stream await this.stopScan(); // Start new stream with selected camera this.stream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: { exact: deviceId }, width: { ideal: 1280 }, height: { ideal: 720 } } }); // Update video and restart scanning if (this.video) { this.video.srcObject = this.stream; await this.video.play(); } this.scanQRCode(); } 
Core Interfaces
interface QRScannerService {
  // ... existing methods ...
  /** Get available cameras */
  getAvailableCameras(): Promise<MediaDeviceInfo[]>;
  
  /** Switch to a specific camera */
  switchCamera(deviceId: string): Promise<void>;
  
  /** Get current camera info */
  getCurrentCamera(): Promise<MediaDeviceInfo | null>;
}
interface QRScannerOptions {
  /** Camera to use ('front' or 'back' for mobile) */
  camera?: "front" | "back";
  /** Whether to show a preview of the camera feed */
  showPreview?: boolean;
  /** Whether to play a sound on successful scan */
  playSound?: boolean;
}
UI Components
The camera switching UI adapts to the platform:
- 
Mobile Interface
- Simple toggle button for front/back cameras
 - Positioned in bottom-right corner
 - Clear visual feedback during switching
 - Native camera controls
 
<button v-if="isNativePlatform" @click="toggleMobileCamera" class="camera-switch-btn" > <font-awesome icon="camera-rotate" /> Switch Camera </button> - 
Desktop Interface
- Dropdown menu with all available cameras
 - Camera labels and device IDs
 - Real-time camera switching
 - Responsive design
 
<select v-model="selectedCameraId" @change="onCameraChange" class="camera-select-dropdown" > <option v-for="camera in availableCameras" :key="camera.deviceId" :value="camera.deviceId" > {{ camera.label || `Camera ${camera.deviceId.slice(0, 4)}` }} </option> </select> 
Error Handling
The camera switching implementation includes comprehensive error handling:
- 
Common Error Scenarios
- Camera in use by another application
 - Permission denied during switch
 - Device not available
 - Stream initialization failure
 - Camera switch timeout
 
 - 
Error Response
private async handleCameraSwitch(deviceId: string): Promise<void> { try { this.updateCameraState("initializing", "Switching camera..."); await this.switchCamera(deviceId); this.updateCameraState("active", "Camera switched successfully"); } catch (error) { this.updateCameraState("error", "Failed to switch camera"); throw error; } } - 
User Feedback
- Visual indicators during switching
 - Error notifications
 - Camera state updates
 - Permission request dialogs
 
 
State Management
The camera system maintains several states:
- 
Camera States
type CameraState = | "initializing" // Camera is being initialized | "ready" // Camera is ready to use | "active" // Camera is actively streaming | "in_use" // Camera is in use by another application | "permission_denied" // Camera permission was denied | "not_found" // No camera found on device | "error" // Generic error state | "off"; // Camera is off - 
State Transitions
- Initialization → Ready
 - Ready → Active
 - Active → Switching
 - Switching → Active/Error
 - Any state → Off (on cleanup)
 
 
Best Practices
- 
Camera Access
- Always check permissions before switching
 - Handle camera busy states
 - Implement proper cleanup
 - Monitor camera state changes
 
 - 
Performance
- Optimize camera resolution
 - Handle stream switching efficiently
 - Manage memory usage
 - Implement proper cleanup
 
 - 
User Experience
- Clear visual feedback
 - Smooth camera transitions
 - Intuitive camera controls
 - Responsive UI updates
 - Accessible camera selection
 
 - 
Security
- Secure camera access
 - Permission management
 - Device validation
 - Stream security
 
 
Testing
- 
Test Scenarios
- Camera switching on both platforms
 - Permission handling
 - Error conditions
 - Multiple camera devices
 - Camera busy states
 - Stream initialization
 - UI responsiveness
 
 - 
Test Environment
- Multiple mobile devices
 - Various desktop browsers
 - Different camera configurations
 - Network conditions
 - Permission states
 
 
Capacitor Implementation Details
MLKit Barcode Scanner Configuration
- 
Plugin Setup
// capacitor.config.ts const config: CapacitorConfig = { plugins: { MLKitBarcodeScanner: { formats: ['QR_CODE'], detectorSize: 1.0, lensFacing: 'back', googleBarcodeScannerModuleInstallState: true, // Additional camera options cameraOptions: { quality: 0.8, allowEditing: false, resultType: 'uri', sourceType: 'CAMERA', saveToGallery: false } } } }; - 
Camera Management
// CapacitorQRScanner.ts export class CapacitorQRScanner implements QRScannerService { private currentLensFacing: LensFacing = LensFacing.Back; async getAvailableCameras(): Promise<MediaDeviceInfo[]> { // On mobile, we have two fixed cameras return [ { deviceId: 'back', label: 'Back Camera', kind: 'videoinput' }, { deviceId: 'front', label: 'Front Camera', kind: 'videoinput' } ] as MediaDeviceInfo[]; } async switchCamera(deviceId: string): Promise<void> { if (!this.isScanning) return; const newLensFacing = deviceId === 'front' ? LensFacing.Front : LensFacing.Back; // Stop current scan await this.stopScan(); // Update lens facing this.currentLensFacing = newLensFacing; // Restart scan with new camera await this.startScan({ camera: deviceId as 'front' | 'back' }); } async getCurrentCamera(): Promise<MediaDeviceInfo | null> { return { deviceId: this.currentLensFacing === LensFacing.Front ? 'front' : 'back', label: this.currentLensFacing === LensFacing.Front ? 'Front Camera' : 'Back Camera', kind: 'videoinput' } as MediaDeviceInfo; } } - 
Camera State Management
// CapacitorQRScanner.ts private async handleCameraState(): Promise<void> { try { // Check if camera is available const { camera } = await BarcodeScanner.checkPermissions(); if (camera === 'denied') { this.updateCameraState('permission_denied'); return; } // Check if camera is in use const isInUse = await this.isCameraInUse(); if (isInUse) { this.updateCameraState('in_use'); return; } this.updateCameraState('ready'); } catch (error) { this.updateCameraState('error', error.message); } } private async isCameraInUse(): Promise<boolean> { try { // Try to start a test scan await BarcodeScanner.startScan({ formats: [BarcodeFormat.QrCode], lensFacing: this.currentLensFacing }); // If successful, stop it immediately await BarcodeScanner.stopScan(); return false; } catch (error) { return error.message.includes('camera in use'); } } - 
Error Handling
// CapacitorQRScanner.ts private async handleCameraError(error: Error): Promise<void> { switch (error.name) { case 'CameraPermissionDenied': this.updateCameraState('permission_denied'); break; case 'CameraInUse': this.updateCameraState('in_use'); break; case 'CameraUnavailable': this.updateCameraState('not_found'); break; default: this.updateCameraState('error', error.message); } } 
Platform-Specific Considerations
- 
iOS Implementation
- Camera permissions in Info.plist
 - Privacy descriptions
 - Camera usage description
 - Background camera access
 
<!-- ios/App/App/Info.plist --> <key>NSCameraUsageDescription</key> <string>We need access to your camera to scan QR codes</string> <key>NSPhotoLibraryUsageDescription</key> <string>We need access to save scanned QR codes</string> - 
Android Implementation
- Camera permissions in AndroidManifest.xml
 - Runtime permission handling
 - Camera features declaration
 - Hardware feature requirements
 
<!-- android/app/src/main/AndroidManifest.xml --> <uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> - 
Platform-Specific Features
- iOS: Camera orientation handling
 - Android: Camera resolution optimization
 - Both: Battery usage optimization
 - Both: Memory management
 
// Platform-specific optimizations private getPlatformSpecificOptions(): StartScanOptions { const baseOptions: StartScanOptions = { formats: [BarcodeFormat.QrCode], lensFacing: this.currentLensFacing }; if (Capacitor.getPlatform() === 'ios') { return { ...baseOptions, // iOS-specific options cameraOptions: { quality: 0.7, // Lower quality for better performance allowEditing: false, resultType: 'uri' } }; } else if (Capacitor.getPlatform() === 'android') { return { ...baseOptions, // Android-specific options cameraOptions: { quality: 0.8, allowEditing: false, resultType: 'uri', saveToGallery: false } }; } return baseOptions; } 
Performance Optimization
- 
Battery Usage
// CapacitorQRScanner.ts private optimizeBatteryUsage(): void { // Reduce scan frequency when battery is low if (this.isLowBattery()) { this.scanInterval = 2000; // 2 seconds between scans } else { this.scanInterval = 1000; // 1 second between scans } } private isLowBattery(): boolean { // Check battery level if available if (Capacitor.isPluginAvailable('Battery')) { const { level } = await Battery.getBatteryLevel(); return level < 0.2; // 20% or lower } return false; } - 
Memory Management
// CapacitorQRScanner.ts private async cleanupResources(): Promise<void> { // Stop scanning await this.stopScan(); // Clear any stored camera data this.currentLensFacing = LensFacing.Back; // Remove listeners this.listenerHandles.forEach(handle => handle()); this.listenerHandles = []; // Reset state this.isScanning = false; this.updateCameraState('off'); } 
Testing on Capacitor
- 
Device Testing
- Test on multiple iOS devices
 - Test on multiple Android devices
 - Test different camera configurations
 - Test with different screen sizes
 - Test with different OS versions
 
 - 
Camera Testing
- Test front camera switching
 - Test back camera switching
 - Test camera permissions
 - Test camera in use scenarios
 - Test low light conditions
 - Test different QR code sizes
 - Test different QR code distances
 
 - 
Performance Testing
- Battery usage monitoring
 - Memory usage monitoring
 - Camera switching speed
 - QR code detection speed
 - App responsiveness
 - Background/foreground transitions