forked from trent_larson/crowd-funder-for-time-pwa
- Update BUILDING.md with current build system information - Modernize various README files across the project - Update CHANGELOG.md with recent changes - Improve documentation consistency and formatting - Update platform-specific documentation (iOS, Electron, Docker) - Enhance test documentation and build guides
834 lines
21 KiB
Markdown
834 lines
21 KiB
Markdown
# 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
|
|
|
|
1. **Factory Pattern**
|
|
- `QRScannerFactory` - Creates appropriate scanner instance based on platform
|
|
- Common interface `QRScannerService` implemented by all scanners
|
|
- Platform detection via Capacitor and build flags
|
|
|
|
2. **Platform-Specific Implementations**
|
|
- `CapacitorQRScanner` - Native mobile implementation using MLKit
|
|
- `WebInlineQRScanner` - Web browser implementation using MediaDevices API
|
|
- `QRScannerDialog.vue` - Shared UI component
|
|
|
|
3. **View Components**
|
|
- `ContactQRScanView` - Dedicated view for scanning QR codes
|
|
- `ContactQRScanShowView` - Combined view for displaying and scanning QR codes
|
|
|
|
## Implementation Details
|
|
|
|
### Core Interfaces
|
|
|
|
```typescript
|
|
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:
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
1. **Initiation**
|
|
- User selects "Scan QR Code" option
|
|
- Platform-specific scanner is initialized
|
|
- Camera permissions are verified
|
|
- Appropriate scanner component is loaded
|
|
|
|
2. **Platform-Specific Implementation**
|
|
- Web: Uses `qrcode-stream` for real-time scanning
|
|
- Native: Uses `@capacitor-mlkit/barcode-scanning`
|
|
|
|
3. **Scanning Process**
|
|
- Camera stream initialization
|
|
- Real-time frame analysis
|
|
- QR code detection and decoding
|
|
- Validation of QR code format
|
|
- Processing of contact information
|
|
|
|
4. **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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```json
|
|
{
|
|
"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
|
|
|
|
1. No camera found
|
|
2. Permission denied
|
|
3. Camera in use by another application
|
|
4. HTTPS required
|
|
5. Browser compatibility issues
|
|
6. Invalid QR code format
|
|
7. Expired QR codes
|
|
8. Duplicate contact attempts
|
|
9. 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
|
|
|
|
1. Always check for camera availability
|
|
2. Request permissions explicitly
|
|
3. Handle all error conditions
|
|
4. Provide clear user feedback
|
|
5. Implement proper cleanup
|
|
|
|
### Performance
|
|
|
|
1. Optimize camera resolution
|
|
2. Implement proper resource cleanup
|
|
3. Handle camera switching efficiently
|
|
4. Manage memory usage
|
|
5. Battery usage optimization
|
|
|
|
### User Experience
|
|
|
|
1. Clear visual feedback
|
|
2. Camera preview
|
|
3. Scanning status indicators
|
|
4. Error messages
|
|
5. Success confirmations
|
|
6. Intuitive camera controls
|
|
7. Smooth camera switching
|
|
8. Responsive UI feedback
|
|
|
|
## Testing
|
|
|
|
### Test Scenarios
|
|
|
|
1. Permission handling
|
|
2. Camera switching
|
|
3. Error conditions
|
|
4. Platform compatibility
|
|
5. Performance metrics
|
|
6. QR code detection
|
|
7. Contact processing
|
|
8. Security validation
|
|
|
|
### Test Environment
|
|
|
|
- Multiple browsers
|
|
- iOS and Android devices
|
|
- Various network conditions
|
|
- Different camera configurations
|
|
|
|
## Dependencies
|
|
|
|
### Key Packages
|
|
|
|
- `@capacitor-mlkit/barcode-scanning`
|
|
- `qrcode-stream`
|
|
- `vue-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
|
|
|
|
1. **Mobile (Capacitor)**
|
|
- Uses `@capacitor-mlkit/barcode-scanning`
|
|
- Supports front/back camera switching
|
|
- Native camera access through platform APIs
|
|
- Optimized for mobile performance
|
|
|
|
```typescript
|
|
// 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);
|
|
}
|
|
```
|
|
|
|
2. **Web (Desktop)**
|
|
- Uses browser's MediaDevices API
|
|
- Supports multiple camera devices
|
|
- Dynamic camera enumeration
|
|
- Real-time camera switching
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
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:
|
|
|
|
1. **Mobile Interface**
|
|
- Simple toggle button for front/back cameras
|
|
- Positioned in bottom-right corner
|
|
- Clear visual feedback during switching
|
|
- Native camera controls
|
|
|
|
```vue
|
|
<button
|
|
v-if="isNativePlatform"
|
|
@click="toggleMobileCamera"
|
|
class="camera-switch-btn"
|
|
>
|
|
<font-awesome icon="camera-rotate" />
|
|
Switch Camera
|
|
</button>
|
|
```
|
|
|
|
2. **Desktop Interface**
|
|
- Dropdown menu with all available cameras
|
|
- Camera labels and device IDs
|
|
- Real-time camera switching
|
|
- Responsive design
|
|
|
|
```vue
|
|
<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:
|
|
|
|
1. **Common Error Scenarios**
|
|
- Camera in use by another application
|
|
- Permission denied during switch
|
|
- Device not available
|
|
- Stream initialization failure
|
|
- Camera switch timeout
|
|
|
|
2. **Error Response**
|
|
|
|
```typescript
|
|
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;
|
|
}
|
|
}
|
|
```
|
|
|
|
3. **User Feedback**
|
|
- Visual indicators during switching
|
|
- Error notifications
|
|
- Camera state updates
|
|
- Permission request dialogs
|
|
|
|
### State Management
|
|
|
|
The camera system maintains several states:
|
|
|
|
1. **Camera States**
|
|
|
|
```typescript
|
|
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
|
|
```
|
|
|
|
2. **State Transitions**
|
|
- Initialization → Ready
|
|
- Ready → Active
|
|
- Active → Switching
|
|
- Switching → Active/Error
|
|
- Any state → Off (on cleanup)
|
|
|
|
### Best Practices
|
|
|
|
1. **Camera Access**
|
|
- Always check permissions before switching
|
|
- Handle camera busy states
|
|
- Implement proper cleanup
|
|
- Monitor camera state changes
|
|
|
|
2. **Performance**
|
|
- Optimize camera resolution
|
|
- Handle stream switching efficiently
|
|
- Manage memory usage
|
|
- Implement proper cleanup
|
|
|
|
3. **User Experience**
|
|
- Clear visual feedback
|
|
- Smooth camera transitions
|
|
- Intuitive camera controls
|
|
- Responsive UI updates
|
|
- Accessible camera selection
|
|
|
|
4. **Security**
|
|
- Secure camera access
|
|
- Permission management
|
|
- Device validation
|
|
- Stream security
|
|
|
|
### Testing
|
|
|
|
1. **Test Scenarios**
|
|
- Camera switching on both platforms
|
|
- Permission handling
|
|
- Error conditions
|
|
- Multiple camera devices
|
|
- Camera busy states
|
|
- Stream initialization
|
|
- UI responsiveness
|
|
|
|
2. **Test Environment**
|
|
- Multiple mobile devices
|
|
- Various desktop browsers
|
|
- Different camera configurations
|
|
- Network conditions
|
|
- Permission states
|
|
|
|
### Capacitor Implementation Details
|
|
|
|
#### MLKit Barcode Scanner Configuration
|
|
|
|
1. **Plugin Setup**
|
|
|
|
```typescript
|
|
// 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
|
|
}
|
|
}
|
|
}
|
|
};
|
|
```
|
|
|
|
2. **Camera Management**
|
|
|
|
```typescript
|
|
// 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;
|
|
}
|
|
}
|
|
```
|
|
|
|
3. **Camera State Management**
|
|
|
|
```typescript
|
|
// 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');
|
|
}
|
|
}
|
|
```
|
|
|
|
4. **Error Handling**
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
1. **iOS Implementation**
|
|
- Camera permissions in Info.plist
|
|
- Privacy descriptions
|
|
- Camera usage description
|
|
- Background camera access
|
|
|
|
```xml
|
|
<!-- 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>
|
|
```
|
|
|
|
2. **Android Implementation**
|
|
- Camera permissions in AndroidManifest.xml
|
|
- Runtime permission handling
|
|
- Camera features declaration
|
|
- Hardware feature requirements
|
|
|
|
```xml
|
|
<!-- 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" />
|
|
```
|
|
|
|
3. **Platform-Specific Features**
|
|
- iOS: Camera orientation handling
|
|
- Android: Camera resolution optimization
|
|
- Both: Battery usage optimization
|
|
- Both: Memory management
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
1. **Battery Usage**
|
|
|
|
```typescript
|
|
// 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;
|
|
}
|
|
```
|
|
|
|
2. **Memory Management**
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
1. **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
|
|
|
|
2. **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
|
|
|
|
3. **Performance Testing**
|
|
- Battery usage monitoring
|
|
- Memory usage monitoring
|
|
- Camera switching speed
|
|
- QR code detection speed
|
|
- App responsiveness
|
|
- Background/foreground transitions
|