30 changed files with 2235 additions and 1951 deletions
@ -1,177 +0,0 @@ |
|||||
--- |
|
||||
description: |
|
||||
globs: |
|
||||
alwaysApply: true |
|
||||
--- |
|
||||
# QR Code Handling Rule |
|
||||
|
|
||||
## Architecture Overview |
|
||||
|
|
||||
The QR code scanning functionality follows a platform-agnostic design using a factory pattern that provides different implementations for web and mobile platforms. |
|
||||
|
|
||||
### 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 |
|
||||
|
|
||||
## Mobile Implementation (Capacitor) |
|
||||
|
|
||||
### Technology Stack |
|
||||
- Uses `@capacitor-mlkit/barcode-scanning` plugin |
|
||||
- Configured in `capacitor.config.ts` |
|
||||
- Native camera access through platform APIs |
|
||||
|
|
||||
### Key Features |
|
||||
- Direct camera access via native APIs |
|
||||
- Optimized for mobile performance |
|
||||
- Supports both iOS and Android |
|
||||
- Real-time QR code detection |
|
||||
- Back camera preferred for scanning |
|
||||
|
|
||||
### Configuration |
|
||||
```typescript |
|
||||
MLKitBarcodeScanner: { |
|
||||
formats: ['QR_CODE'], |
|
||||
detectorSize: 1.0, |
|
||||
lensFacing: 'back', |
|
||||
googleBarcodeScannerModuleInstallState: true |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
### Permissions Handling |
|
||||
1. Check permissions via `BarcodeScanner.checkPermissions()` |
|
||||
2. Request permissions if needed |
|
||||
3. Handle permission states (granted/denied) |
|
||||
4. Graceful fallbacks for permission issues |
|
||||
|
|
||||
## Web Implementation |
|
||||
|
|
||||
### Technology Stack |
|
||||
- Uses browser's MediaDevices API |
|
||||
- Vue.js components for UI |
|
||||
- EventEmitter for stream management |
|
||||
|
|
||||
### Key Features |
|
||||
- Browser-based camera access |
|
||||
- Inline camera preview |
|
||||
- Responsive design |
|
||||
- Cross-browser compatibility |
|
||||
- Progressive enhancement |
|
||||
|
|
||||
### Permissions Handling |
|
||||
1. Uses browser's permission API |
|
||||
2. MediaDevices API for camera access |
|
||||
3. Handles secure context requirements |
|
||||
4. Provides user feedback for permission states |
|
||||
|
|
||||
## Shared Features |
|
||||
|
|
||||
### Error Handling |
|
||||
1. Permission denied scenarios |
|
||||
2. Device compatibility checks |
|
||||
3. Camera access failures |
|
||||
4. QR code validation |
|
||||
5. Network connectivity issues |
|
||||
|
|
||||
### User Experience |
|
||||
1. Clear feedback during scanning |
|
||||
2. Loading states |
|
||||
3. Error messages |
|
||||
4. Success confirmations |
|
||||
5. Camera preview |
|
||||
|
|
||||
### Security |
|
||||
1. HTTPS requirement for web |
|
||||
2. Permission validation |
|
||||
3. Data validation |
|
||||
4. Safe error handling |
|
||||
|
|
||||
## Usage Guidelines |
|
||||
|
|
||||
### Platform Detection |
|
||||
```typescript |
|
||||
const isNative = QRScannerFactory.isNativePlatform(); |
|
||||
if (isNative) { |
|
||||
// Use native scanner |
|
||||
} else { |
|
||||
// Use web scanner |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
### Implementation Example |
|
||||
```typescript |
|
||||
const scanner = QRScannerFactory.getInstance(); |
|
||||
await scanner.checkPermissions(); |
|
||||
await scanner.startScan(); |
|
||||
scanner.addListener({ |
|
||||
onScan: (result) => { |
|
||||
// Handle scan result |
|
||||
}, |
|
||||
onError: (error) => { |
|
||||
// Handle error |
|
||||
} |
|
||||
}); |
|
||||
``` |
|
||||
|
|
||||
### Best Practices |
|
||||
1. Always check permissions before starting scan |
|
||||
2. Clean up resources after scanning |
|
||||
3. Handle all error cases |
|
||||
4. Provide clear user feedback |
|
||||
5. Test on multiple devices/browsers |
|
||||
|
|
||||
## Platform-Specific Notes |
|
||||
|
|
||||
### Mobile (Capacitor) |
|
||||
1. Use native camera API when available |
|
||||
2. Handle device rotation |
|
||||
3. Support both front/back cameras |
|
||||
4. Manage system permissions properly |
|
||||
5. Handle app lifecycle events |
|
||||
|
|
||||
### Web |
|
||||
1. Check browser compatibility |
|
||||
2. Handle secure context requirement |
|
||||
3. Manage memory usage |
|
||||
4. Clean up MediaStream |
|
||||
5. Handle tab visibility changes |
|
||||
|
|
||||
## Testing Requirements |
|
||||
|
|
||||
1. Test on multiple devices |
|
||||
2. Verify permission flows |
|
||||
3. Check error handling |
|
||||
4. Validate cleanup |
|
||||
5. Verify cross-platform behavior |
|
||||
|
|
||||
## Service Interface |
|
||||
|
|
||||
```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>; |
|
||||
} |
|
||||
|
|
||||
interface ScanListener { |
|
||||
onScan: (result: string) => void; |
|
||||
onError?: (error: Error) => void; |
|
||||
} |
|
||||
|
|
||||
interface QRScannerOptions { |
|
||||
camera?: "front" | "back"; |
|
||||
showPreview?: boolean; |
|
||||
playSound?: boolean; |
|
||||
} |
|
@ -1,533 +0,0 @@ |
|||||
--- |
|
||||
description: |
|
||||
globs: |
|
||||
alwaysApply: true |
|
||||
--- |
|
||||
# QR Code Implementation Guide |
|
||||
|
|
||||
## 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 |
|
||||
``` |
|
||||
|
|
||||
## Core Interfaces |
|
||||
|
|
||||
```typescript |
|
||||
// types.ts |
|
||||
export interface ScanListener { |
|
||||
onScan: (result: string) => void; |
|
||||
onError?: (error: Error) => void; |
|
||||
} |
|
||||
|
|
||||
export interface QRScannerOptions { |
|
||||
camera?: "front" | "back"; |
|
||||
showPreview?: boolean; |
|
||||
playSound?: boolean; |
|
||||
} |
|
||||
|
|
||||
export 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>; |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
## Configuration Files |
|
||||
|
|
||||
### Vite Configuration |
|
||||
```typescript |
|
||||
// vite.config.common.mts |
|
||||
export function createBuildConfig(mode: string) { |
|
||||
return { |
|
||||
define: { |
|
||||
'process.env.VITE_PLATFORM': JSON.stringify(mode), |
|
||||
'process.env.VITE_PWA_ENABLED': JSON.stringify(!isNative), |
|
||||
__IS_MOBILE__: JSON.stringify(isCapacitor), |
|
||||
__USE_QR_READER__: JSON.stringify(!isCapacitor) |
|
||||
} |
|
||||
}; |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
### 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 |
|
||||
``` |
|
||||
|
|
||||
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; |
|
||||
|
|
||||
private static isNativePlatform(): boolean { |
|
||||
const capacitorNative = Capacitor.isNativePlatform(); |
|
||||
const isMobile = typeof __IS_MOBILE__ !== "undefined" ? __IS_MOBILE__ : capacitorNative; |
|
||||
const platform = Capacitor.getPlatform(); |
|
||||
|
|
||||
// Always use native scanner on Android/iOS |
|
||||
if (platform === "android" || platform === "ios") { |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
// For other platforms, use native if available |
|
||||
return capacitorNative || isMobile; |
|
||||
} |
|
||||
|
|
||||
static getInstance(): QRScannerService { |
|
||||
if (!this.instance) { |
|
||||
const isNative = this.isNativePlatform(); |
|
||||
|
|
||||
if (isNative) { |
|
||||
this.instance = new CapacitorQRScanner(); |
|
||||
} else { |
|
||||
this.instance = new WebInlineQRScanner(); |
|
||||
} |
|
||||
} |
|
||||
return this.instance!; |
|
||||
} |
|
||||
|
|
||||
static async cleanup(): Promise<void> { |
|
||||
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<void>> = []; |
|
||||
private cleanupPromise: Promise<void> | null = null; |
|
||||
|
|
||||
async checkPermissions(): Promise<boolean> { |
|
||||
try { |
|
||||
const { camera } = await BarcodeScanner.checkPermissions(); |
|
||||
return camera === "granted"; |
|
||||
} catch (error) { |
|
||||
logger.error("Error checking camera permissions:", error); |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async requestPermissions(): Promise<boolean> { |
|
||||
try { |
|
||||
if (await this.checkPermissions()) { |
|
||||
return true; |
|
||||
} |
|
||||
const { camera } = await BarcodeScanner.requestPermissions(); |
|
||||
return camera === "granted"; |
|
||||
} catch (error) { |
|
||||
logger.error("Error requesting camera permissions:", error); |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async isSupported(): Promise<boolean> { |
|
||||
try { |
|
||||
const { supported } = await BarcodeScanner.isSupported(); |
|
||||
return supported; |
|
||||
} catch (error) { |
|
||||
logger.error("Error checking scanner support:", error); |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async startScan(options?: QRScannerOptions): Promise<void> { |
|
||||
if (this.isScanning) return; |
|
||||
if (this.cleanupPromise) { |
|
||||
await this.cleanupPromise; |
|
||||
} |
|
||||
|
|
||||
try { |
|
||||
if (!(await this.checkPermissions())) { |
|
||||
const granted = await this.requestPermissions(); |
|
||||
if (!granted) { |
|
||||
throw new Error("Camera permission denied"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (!(await this.isSupported())) { |
|
||||
throw new Error("QR scanning not supported on this device"); |
|
||||
} |
|
||||
|
|
||||
this.isScanning = true; |
|
||||
|
|
||||
const scanOptions: StartScanOptions = { |
|
||||
formats: [BarcodeFormat.QrCode], |
|
||||
lensFacing: options?.camera === "front" ? LensFacing.Front : LensFacing.Back, |
|
||||
}; |
|
||||
|
|
||||
const handle = await BarcodeScanner.addListener("barcodeScanned", (result) => { |
|
||||
if (this.scanListener && result.barcode?.rawValue) { |
|
||||
this.scanListener.onScan(result.barcode.rawValue); |
|
||||
} |
|
||||
}); |
|
||||
this.listenerHandles.push(handle.remove); |
|
||||
|
|
||||
await BarcodeScanner.startScan(scanOptions); |
|
||||
} catch (error) { |
|
||||
this.isScanning = false; |
|
||||
await this.cleanup(); |
|
||||
this.scanListener?.onError?.(error instanceof Error ? error : new Error(String(error))); |
|
||||
throw error; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async stopScan(): Promise<void> { |
|
||||
if (!this.isScanning) return; |
|
||||
this.isScanning = false; |
|
||||
|
|
||||
try { |
|
||||
await BarcodeScanner.stopScan(); |
|
||||
} catch (error) { |
|
||||
logger.error("Error stopping scan:", error); |
|
||||
throw error; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
addListener(listener: ScanListener): void { |
|
||||
this.scanListener = listener; |
|
||||
} |
|
||||
|
|
||||
onStream(callback: (stream: MediaStream | null) => void): void { |
|
||||
// No-op for native scanner |
|
||||
callback(null); |
|
||||
} |
|
||||
|
|
||||
async cleanup(): Promise<void> { |
|
||||
await this.stopScan(); |
|
||||
for (const handle of this.listenerHandles) { |
|
||||
await handle(); |
|
||||
} |
|
||||
this.listenerHandles = []; |
|
||||
this.scanListener = null; |
|
||||
} |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
5. **Implement Web Scanner** |
|
||||
```typescript |
|
||||
// WebInlineQRScanner.ts |
|
||||
export class WebInlineQRScanner implements QRScannerService { |
|
||||
private scanListener: ScanListener | null = null; |
|
||||
private isScanning = false; |
|
||||
private stream: MediaStream | null = null; |
|
||||
private events = new EventEmitter(); |
|
||||
|
|
||||
constructor(private options?: QRScannerOptions) {} |
|
||||
|
|
||||
async checkPermissions(): Promise<boolean> { |
|
||||
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; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async requestPermissions(): Promise<boolean> { |
|
||||
try { |
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ |
|
||||
video: { |
|
||||
facingMode: "environment", |
|
||||
width: { ideal: 1280 }, |
|
||||
height: { ideal: 720 }, |
|
||||
}, |
|
||||
}); |
|
||||
stream.getTracks().forEach(track => track.stop()); |
|
||||
return true; |
|
||||
} catch (error) { |
|
||||
logger.error("Error requesting camera permissions:", error); |
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async isSupported(): Promise<boolean> { |
|
||||
return 'mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices; |
|
||||
} |
|
||||
|
|
||||
async startScan(): Promise<void> { |
|
||||
if (this.isScanning) return; |
|
||||
|
|
||||
try { |
|
||||
this.isScanning = true; |
|
||||
this.stream = await navigator.mediaDevices.getUserMedia({ |
|
||||
video: { |
|
||||
facingMode: "environment", |
|
||||
width: { ideal: 1280 }, |
|
||||
height: { ideal: 720 }, |
|
||||
}, |
|
||||
}); |
|
||||
this.events.emit("stream", this.stream); |
|
||||
} catch (error) { |
|
||||
this.isScanning = false; |
|
||||
const wrappedError = error instanceof Error ? error : new Error(String(error)); |
|
||||
this.scanListener?.onError?.(wrappedError); |
|
||||
throw wrappedError; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async stopScan(): Promise<void> { |
|
||||
if (!this.isScanning) return; |
|
||||
|
|
||||
try { |
|
||||
if (this.stream) { |
|
||||
this.stream.getTracks().forEach(track => track.stop()); |
|
||||
this.stream = null; |
|
||||
} |
|
||||
this.events.emit("stream", null); |
|
||||
} catch (error) { |
|
||||
logger.error("Error stopping scan:", error); |
|
||||
throw error; |
|
||||
} finally { |
|
||||
this.isScanning = false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
addListener(listener: ScanListener): void { |
|
||||
this.scanListener = listener; |
|
||||
} |
|
||||
|
|
||||
onStream(callback: (stream: MediaStream | null) => void): void { |
|
||||
this.events.on("stream", callback); |
|
||||
} |
|
||||
|
|
||||
async cleanup(): Promise<void> { |
|
||||
try { |
|
||||
await this.stopScan(); |
|
||||
this.events.removeAllListeners(); |
|
||||
} catch (error) { |
|
||||
logger.error("Error during cleanup:", error); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
## Usage Example |
|
||||
|
|
||||
```typescript |
|
||||
// Example usage in a Vue component |
|
||||
import { QRScannerFactory } from '@/services/QRScanner/QRScannerFactory'; |
|
||||
|
|
||||
export default defineComponent({ |
|
||||
async mounted() { |
|
||||
const scanner = QRScannerFactory.getInstance(); |
|
||||
|
|
||||
try { |
|
||||
// Check and request permissions |
|
||||
if (!(await scanner.checkPermissions())) { |
|
||||
const granted = await scanner.requestPermissions(); |
|
||||
if (!granted) { |
|
||||
throw new Error('Camera permission denied'); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Add scan listener |
|
||||
scanner.addListener({ |
|
||||
onScan: (result) => { |
|
||||
console.log('QR Code scanned:', result); |
|
||||
}, |
|
||||
onError: (error) => { |
|
||||
console.error('Scan error:', error); |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
// Start scanning |
|
||||
await scanner.startScan({ |
|
||||
camera: 'back', |
|
||||
showPreview: true |
|
||||
}); |
|
||||
|
|
||||
// Handle stream for preview |
|
||||
scanner.onStream((stream) => { |
|
||||
if (stream) { |
|
||||
// Update video element with stream |
|
||||
this.videoElement.srcObject = stream; |
|
||||
} |
|
||||
}); |
|
||||
} catch (error) { |
|
||||
console.error('Failed to start scanner:', error); |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
async beforeUnmount() { |
|
||||
// Clean up scanner |
|
||||
await QRScannerFactory.cleanup(); |
|
||||
} |
|
||||
}); |
|
||||
``` |
|
||||
|
|
||||
## Best Practices |
|
||||
|
|
||||
1. **Error Handling** |
|
||||
- Always implement error handlers in scan listeners |
|
||||
- Handle permission denials gracefully |
|
||||
- Provide user feedback for errors |
|
||||
- Clean up resources on errors |
|
||||
|
|
||||
2. **Resource Management** |
|
||||
- Always call cleanup when done |
|
||||
- Stop camera streams properly |
|
||||
- Remove event listeners |
|
||||
- Handle component unmounting |
|
||||
|
|
||||
3. **Performance** |
|
||||
- Use appropriate camera resolution |
|
||||
- Clean up resources promptly |
|
||||
- Handle platform-specific optimizations |
|
||||
- Monitor memory usage |
|
||||
|
|
||||
4. **Security** |
|
||||
- Require HTTPS for web implementation |
|
||||
- Validate scanned data |
|
||||
- Handle permissions properly |
|
||||
- Sanitize user input |
|
||||
|
|
||||
5. **Testing** |
|
||||
- Test on multiple devices |
|
||||
- Verify permission flows |
|
||||
- Check error scenarios |
|
||||
- Validate cleanup |
|
||||
- Test cross-platform behavior |
|
||||
|
|
||||
## 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 |
|
||||
- Provides native UI for scanning |
|
||||
|
|
||||
### Web |
|
||||
- Uses MediaDevices API |
|
||||
- Requires HTTPS for camera access |
|
||||
- Handles browser compatibility |
|
||||
- Manages memory and resources |
|
||||
- Provides fallback UI |
|
||||
- Uses vue-qrcode-reader for web scanning |
|
||||
|
|
||||
## Testing |
|
||||
|
|
||||
1. **Unit Tests** |
|
||||
- Test factory pattern |
|
||||
- Test platform detection |
|
||||
- Test error handling |
|
||||
- Test cleanup procedures |
|
||||
- Test permission flows |
|
||||
|
|
||||
2. **Integration Tests** |
|
||||
- Test camera access |
|
||||
- Test QR code detection |
|
||||
- Test cross-platform behavior |
|
||||
- Test UI components |
|
||||
- Test error scenarios |
|
||||
|
|
||||
3. **E2E Tests** |
|
||||
- Test complete scanning flow |
|
||||
- Test permission handling |
|
||||
- Test cross-platform compatibility |
|
||||
- Test error recovery |
|
||||
- Test cleanup procedures |
|
||||
|
|
||||
## Best Practices |
|
||||
|
|
||||
1. **Error Handling** |
|
||||
- Always handle permission errors gracefully |
|
||||
- Provide clear error messages to users |
|
||||
- Implement proper cleanup on errors |
|
||||
- Log errors for debugging |
|
||||
|
|
||||
2. **Performance** |
|
||||
- Clean up resources when not in use |
|
||||
- Handle device rotation properly |
|
||||
- Optimize camera usage |
|
||||
- Manage memory efficiently |
|
||||
|
|
||||
3. **Security** |
|
||||
- Request minimum required permissions |
|
||||
- Handle sensitive data securely |
|
||||
- Validate scanned data |
|
||||
- Implement proper cleanup |
|
||||
|
|
||||
4. **User Experience** |
|
||||
- Provide clear feedback |
|
||||
- Handle edge cases gracefully |
|
||||
- Support both platforms seamlessly |
|
||||
- Implement proper loading states |
|
||||
|
|
||||
## Troubleshooting |
|
||||
|
|
||||
1. **Common Issues** |
|
||||
- Camera permissions denied |
|
||||
- Device not supported |
|
||||
- Scanner not working |
|
||||
- Memory leaks |
|
||||
- UI glitches |
|
||||
|
|
||||
2. **Solutions** |
|
||||
- Check permissions |
|
||||
- Verify device support |
|
||||
- Debug scanner implementation |
|
||||
- Monitor memory usage |
|
||||
- Test UI components |
|
||||
|
|
||||
## Maintenance |
|
||||
|
|
||||
1. **Regular Updates** |
|
||||
- Keep dependencies updated |
|
||||
- Monitor platform changes |
|
||||
- Update documentation |
|
||||
- Review security patches |
|
||||
|
|
||||
2. **Performance Monitoring** |
|
||||
- Track memory usage |
|
||||
- Monitor camera performance |
|
||||
- Check error rates |
|
||||
- Analyze user feedback |
|
@ -1,6 +1,21 @@ |
|||||
{ |
{ |
||||
"appId": "com.brownspank.timesafari", |
"appId": "app.timesafari", |
||||
"appName": "TimeSafari", |
"appName": "TimeSafari", |
||||
"webDir": "dist", |
"webDir": "dist", |
||||
"bundledWebRuntime": false |
"bundledWebRuntime": false, |
||||
|
"server": { |
||||
|
"cleartext": true |
||||
|
}, |
||||
|
"plugins": { |
||||
|
"App": { |
||||
|
"appUrlOpen": { |
||||
|
"handlers": [ |
||||
|
{ |
||||
|
"url": "timesafari://*", |
||||
|
"autoVerify": true |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
|
@ -1,6 +1,6 @@ |
|||||
JWT Creation & Verification |
JWT Creation & Verification |
||||
|
|
||||
To run this in a script, see ./openssl_signing_console.sh |
To run this in a script, see /scripts/openssl_signing_console.sh |
||||
|
|
||||
Prerequisites: openssl, jq |
Prerequisites: openssl, jq |
||||
|
|
@ -0,0 +1,805 @@ |
|||||
|
# 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), |
||||
|
'process.env.VITE_PWA_ENABLED': JSON.stringify(!isNative), |
||||
|
__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 |
@ -1,507 +0,0 @@ |
|||||
# Camera Implementation Documentation |
|
||||
|
|
||||
## Overview |
|
||||
|
|
||||
This document describes how camera functionality is implemented across the TimeSafari application. The application uses cameras for several purposes: |
|
||||
|
|
||||
1. QR Code scanning for contact sharing and verification |
|
||||
2. Photo capture for gift records |
|
||||
3. Profile photo management |
|
||||
4. Shared photo handling |
|
||||
5. Image upload and processing |
|
||||
|
|
||||
## Components |
|
||||
|
|
||||
### QRScannerDialog.vue |
|
||||
|
|
||||
Primary component for QR code scanning in web browsers. |
|
||||
|
|
||||
**Key Features:** |
|
||||
|
|
||||
- Uses `qrcode-stream` for web-based QR scanning |
|
||||
- Supports both front and back cameras |
|
||||
- Provides real-time camera status feedback |
|
||||
- Implements error handling with user-friendly messages |
|
||||
- Includes camera switching functionality |
|
||||
|
|
||||
**Camera Access Flow:** |
|
||||
|
|
||||
1. Checks for camera API availability |
|
||||
2. Enumerates available video devices |
|
||||
3. Requests camera permissions |
|
||||
4. Initializes camera stream with preferred settings |
|
||||
5. Handles various error conditions with specific messages |
|
||||
|
|
||||
### PhotoDialog.vue |
|
||||
|
|
||||
Component for photo capture and selection. |
|
||||
|
|
||||
**Key Features:** |
|
||||
|
|
||||
- Cross-platform photo capture interface |
|
||||
- Image cropping capabilities |
|
||||
- File selection fallback |
|
||||
- Unified interface for different platforms |
|
||||
- Progress feedback during upload |
|
||||
- Comprehensive error handling |
|
||||
|
|
||||
**Camera Access Flow:** |
|
||||
|
|
||||
1. User initiates photo capture |
|
||||
2. Platform-specific camera access is requested |
|
||||
3. Image is captured or selected |
|
||||
4. Optional cropping is performed |
|
||||
5. Image is processed and uploaded |
|
||||
6. URL is returned to caller |
|
||||
|
|
||||
### ImageMethodDialog.vue |
|
||||
|
|
||||
Component for selecting image input method. |
|
||||
|
|
||||
**Key Features:** |
|
||||
- Multiple input methods (camera, file upload, URL) |
|
||||
- Unified interface for image selection |
|
||||
- Integration with PhotoDialog for processing |
|
||||
- Support for image cropping |
|
||||
- URL-based image handling |
|
||||
|
|
||||
**Camera Access Flow:** |
|
||||
|
|
||||
1. User selects camera option |
|
||||
2. PhotoDialog is opened for capture |
|
||||
3. Captured image is processed |
|
||||
4. Image is returned to parent component |
|
||||
|
|
||||
### SharedPhotoView.vue |
|
||||
|
|
||||
Component for handling shared photos. |
|
||||
|
|
||||
**Key Features:** |
|
||||
- Processes incoming shared photos |
|
||||
- Options to use photo for gifts or profile |
|
||||
- Image preview and confirmation |
|
||||
- Server upload integration |
|
||||
- Temporary storage management |
|
||||
|
|
||||
**Photo Processing Flow:** |
|
||||
|
|
||||
1. Photo is shared to application |
|
||||
2. Stored temporarily in IndexedDB |
|
||||
3. User chooses usage (gift/profile) |
|
||||
4. Image is processed accordingly |
|
||||
5. Server upload is performed |
|
||||
|
|
||||
### ContactQRScanShowView.vue |
|
||||
|
|
||||
Component for QR code scanning in contact sharing. |
|
||||
|
|
||||
**Key Features:** |
|
||||
- QR code scanning interface |
|
||||
- Camera controls (start/stop) |
|
||||
- Platform-specific implementations |
|
||||
- Error handling and status feedback |
|
||||
|
|
||||
**Camera Access Flow:** |
|
||||
|
|
||||
1. User initiates scanning |
|
||||
2. Camera permissions are checked |
|
||||
3. Camera stream is initialized |
|
||||
4. QR codes are detected in real-time |
|
||||
5. Results are processed |
|
||||
|
|
||||
## Services |
|
||||
|
|
||||
### QRScanner Services |
|
||||
|
|
||||
#### WebDialogQRScanner |
|
||||
|
|
||||
Web-based implementation of QR scanning. |
|
||||
|
|
||||
**Key Methods:** |
|
||||
|
|
||||
- `checkPermissions()`: Verifies camera permission status |
|
||||
- `requestPermissions()`: Requests camera access |
|
||||
- `isSupported()`: Checks for camera API support |
|
||||
- Handles various error conditions with specific messages |
|
||||
|
|
||||
#### CapacitorQRScanner |
|
||||
|
|
||||
Native implementation using Capacitor's MLKit. |
|
||||
|
|
||||
**Key Features:** |
|
||||
|
|
||||
- Uses `@capacitor-mlkit/barcode-scanning` |
|
||||
- Supports both front and back cameras |
|
||||
- Implements permission management |
|
||||
- Provides continuous scanning capability |
|
||||
|
|
||||
### Platform Services |
|
||||
|
|
||||
#### WebPlatformService |
|
||||
|
|
||||
Web-specific implementation of platform features. |
|
||||
|
|
||||
**Camera Capabilities:** |
|
||||
|
|
||||
- Uses HTML5 file input with capture attribute for mobile |
|
||||
- Uses getUserMedia API for desktop webcam access |
|
||||
- Falls back to file selection if camera unavailable |
|
||||
- Processes captured images for consistent format |
|
||||
- Handles both mobile and desktop browser environments |
|
||||
|
|
||||
#### CapacitorPlatformService |
|
||||
|
|
||||
Native implementation using Capacitor. |
|
||||
|
|
||||
**Camera Features:** |
|
||||
|
|
||||
- Uses `Camera.getPhoto()` for native camera access |
|
||||
- Supports image editing |
|
||||
- Configures high-quality image capture |
|
||||
- Handles base64 image processing |
|
||||
- Provides native camera UI |
|
||||
|
|
||||
#### ElectronPlatformService |
|
||||
|
|
||||
Desktop implementation (currently unimplemented). |
|
||||
|
|
||||
**Status:** |
|
||||
|
|
||||
- Camera functionality not yet implemented |
|
||||
- Planned to use Electron's media APIs |
|
||||
- Will support desktop camera access |
|
||||
|
|
||||
## Camera Usage Scenarios |
|
||||
|
|
||||
### Gift Photo Capture |
|
||||
|
|
||||
**Implementation:** |
|
||||
- Uses PhotoDialog for capture/selection |
|
||||
- Supports multiple input methods |
|
||||
- Optional image cropping |
|
||||
- Server upload with authentication |
|
||||
- Integration with gift records |
|
||||
|
|
||||
**Flow:** |
|
||||
1. User initiates photo capture from gift details |
|
||||
2. ImageMethodDialog presents input options |
|
||||
3. PhotoDialog handles capture/selection |
|
||||
4. Image is processed and uploaded |
|
||||
5. URL is attached to gift record |
|
||||
|
|
||||
### Profile Photo Management |
|
||||
|
|
||||
**Implementation:** |
|
||||
- Uses same PhotoDialog component |
|
||||
- Enforces square aspect ratio |
|
||||
- Requires image cropping |
|
||||
- Updates user profile settings |
|
||||
- Handles profile image updates |
|
||||
|
|
||||
**Flow:** |
|
||||
1. User initiates profile photo update |
|
||||
2. PhotoDialog opens with cropping enabled |
|
||||
3. Image is captured/selected |
|
||||
4. User crops to square aspect ratio |
|
||||
5. Image is uploaded and profile updated |
|
||||
|
|
||||
### Shared Photo Processing |
|
||||
|
|
||||
**Implementation:** |
|
||||
- Handles incoming shared photos |
|
||||
- Temporary storage in IndexedDB |
|
||||
- Options for photo usage |
|
||||
- Server upload integration |
|
||||
- Cleanup after processing |
|
||||
|
|
||||
**Flow:** |
|
||||
1. Photo is shared to application |
|
||||
2. Stored temporarily in IndexedDB |
|
||||
3. SharedPhotoView presents options |
|
||||
4. User chooses usage (gift/profile) |
|
||||
5. Image is processed accordingly |
|
||||
|
|
||||
### QR Code Scanning |
|
||||
|
|
||||
**Implementation:** |
|
||||
- Platform-specific scanning components |
|
||||
- Real-time camera feed processing |
|
||||
- QR code detection and validation |
|
||||
- Contact information processing |
|
||||
- Error handling and retry |
|
||||
|
|
||||
**Flow:** |
|
||||
1. User initiates QR scanning |
|
||||
2. Camera permissions are checked |
|
||||
3. Camera stream is initialized |
|
||||
4. QR codes are detected |
|
||||
5. Contact information is processed |
|
||||
|
|
||||
### QR Code Workflow |
|
||||
|
|
||||
**Implementation Details:** |
|
||||
|
|
||||
The QR code scanning workflow is implemented across multiple components and services to provide a seamless experience for contact sharing and verification. The system supports both web-based and native implementations through platform-specific services. |
|
||||
|
|
||||
#### QR Code Generation |
|
||||
|
|
||||
**Contact QR Codes:** |
|
||||
- Generated using `qrcode.vue` component |
|
||||
- Contains encrypted contact information |
|
||||
- Includes user ID and verification data |
|
||||
- Supports offline sharing |
|
||||
- Implements error correction |
|
||||
|
|
||||
**QR Code Format:** |
|
||||
```json |
|
||||
{ |
|
||||
"type": "contact", |
|
||||
"userId": "encrypted_user_id", |
|
||||
"timestamp": "creation_time", |
|
||||
"version": "qr_code_version", |
|
||||
"data": "encrypted_contact_data" |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
#### QR Code Scanning 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 Implementation:* |
|
||||
- Uses `qrcode-stream` for real-time scanning |
|
||||
- Supports both front and back cameras |
|
||||
- Implements continuous scanning |
|
||||
- Provides visual feedback for scanning status |
|
||||
- Handles browser compatibility issues |
|
||||
|
|
||||
*Native Implementation (Capacitor):* |
|
||||
- Uses `@capacitor-mlkit/barcode-scanning` |
|
||||
- Leverages native camera capabilities |
|
||||
- Provides optimized scanning performance |
|
||||
- Supports multiple barcode formats |
|
||||
- Implements native permission handling |
|
||||
|
|
||||
**3. Scanning Process:** |
|
||||
- Camera stream is initialized |
|
||||
- Real-time frame analysis begins |
|
||||
- QR codes are detected and decoded |
|
||||
- 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 |
|
||||
|
|
||||
**5. Error Handling:** |
|
||||
- Invalid QR code format |
|
||||
- Expired QR codes |
|
||||
- Duplicate contact attempts |
|
||||
- Network connectivity issues |
|
||||
- Permission denials |
|
||||
- Camera access problems |
|
||||
|
|
||||
**6. Success Flow:** |
|
||||
- Contact information is extracted |
|
||||
- User is prompted for confirmation |
|
||||
- Contact is added to user's list |
|
||||
- Success notification is displayed |
|
||||
- Camera resources are cleaned up |
|
||||
|
|
||||
#### Security Measures |
|
||||
|
|
||||
**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 |
|
||||
|
|
||||
#### User Experience |
|
||||
|
|
||||
**Scanning Interface:** |
|
||||
- Clear visual feedback |
|
||||
- Camera preview |
|
||||
- Scanning status indicators |
|
||||
- Error messages |
|
||||
- Success confirmations |
|
||||
|
|
||||
**Accessibility:** |
|
||||
- Support for different screen sizes |
|
||||
- Clear instructions |
|
||||
- Error recovery options |
|
||||
- Alternative input methods |
|
||||
- Offline capability |
|
||||
|
|
||||
#### Performance Considerations |
|
||||
|
|
||||
**Optimization:** |
|
||||
- Efficient camera resource usage |
|
||||
- Quick QR code detection |
|
||||
- Minimal processing overhead |
|
||||
- Battery usage optimization |
|
||||
- Memory management |
|
||||
|
|
||||
**Platform-Specific Optimizations:** |
|
||||
- Web: Optimized for browser performance |
|
||||
- Native: Leverages device capabilities |
|
||||
- Desktop: Efficient resource usage |
|
||||
- Mobile: Battery and performance balance |
|
||||
|
|
||||
## Platform-Specific Considerations |
|
||||
|
|
||||
### iOS |
|
||||
|
|
||||
- Requires `NSCameraUsageDescription` in Info.plist |
|
||||
- Supports both front and back cameras |
|
||||
- Implements proper permission handling |
|
||||
- Uses native camera UI through Capacitor |
|
||||
- Handles photo library access |
|
||||
|
|
||||
### Android |
|
||||
|
|
||||
- Requires camera permissions in manifest |
|
||||
- Supports both front and back cameras |
|
||||
- Handles permission requests through Capacitor |
|
||||
- Uses native camera UI |
|
||||
- Manages photo library access |
|
||||
|
|
||||
### Web |
|
||||
|
|
||||
- Requires HTTPS for camera access |
|
||||
- Implements fallback mechanisms |
|
||||
- Handles browser compatibility issues |
|
||||
- Uses getUserMedia API on desktop |
|
||||
- Uses file input with capture on mobile |
|
||||
- Supports multiple input methods |
|
||||
|
|
||||
## 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. Upload failures |
|
||||
7. Image processing errors |
|
||||
|
|
||||
### Error Response |
|
||||
|
|
||||
- User-friendly error messages |
|
||||
- Troubleshooting tips |
|
||||
- Clear instructions for resolution |
|
||||
- Platform-specific guidance |
|
||||
- Graceful fallbacks |
|
||||
|
|
||||
## Security Considerations |
|
||||
|
|
||||
### Permission Management |
|
||||
|
|
||||
- Explicit permission requests |
|
||||
- Permission state tracking |
|
||||
- Graceful handling of denied permissions |
|
||||
- Platform-specific permission handling |
|
||||
- Secure permission storage |
|
||||
|
|
||||
### Data Handling |
|
||||
|
|
||||
- Secure image processing |
|
||||
- Proper cleanup of camera resources |
|
||||
- No persistent storage of camera data |
|
||||
- Secure server upload |
|
||||
- Temporary storage management |
|
||||
|
|
||||
## 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 |
|
||||
6. Use platform-specific optimizations |
|
||||
|
|
||||
### Performance |
|
||||
|
|
||||
1. Optimize camera resolution |
|
||||
2. Implement proper resource cleanup |
|
||||
3. Handle camera switching efficiently |
|
||||
4. Manage memory usage |
|
||||
5. Optimize image processing |
|
||||
6. Handle upload efficiently |
|
||||
|
|
||||
### User Experience |
|
||||
|
|
||||
1. Clear status indicators |
|
||||
2. Intuitive camera controls |
|
||||
3. Helpful error messages |
|
||||
4. Smooth camera switching |
|
||||
5. Responsive UI feedback |
|
||||
6. Platform-appropriate UI |
|
||||
|
|
||||
## Future Improvements |
|
||||
|
|
||||
### Planned Enhancements |
|
||||
|
|
||||
1. Implement Electron camera support |
|
||||
2. Add advanced camera features |
|
||||
3. Improve error handling |
|
||||
4. Enhance user feedback |
|
||||
5. Optimize performance |
|
||||
6. Add image compression options |
|
||||
|
|
||||
### Known Issues |
|
||||
|
|
||||
1. Electron camera implementation pending |
|
||||
2. Some browser compatibility limitations |
|
||||
3. Platform-specific quirks to address |
|
||||
4. Mobile browser camera access limitations |
|
||||
5. Image upload performance on slow connections |
|
||||
|
|
||||
## Dependencies |
|
||||
|
|
||||
### Key Packages |
|
||||
|
|
||||
- `@capacitor-mlkit/barcode-scanning` |
|
||||
- `qrcode-stream` |
|
||||
- `vue-picture-cropper` |
|
||||
- `@capacitor/camera` |
|
||||
- Platform-specific camera APIs |
|
||||
|
|
||||
## Testing |
|
||||
|
|
||||
### Test Scenarios |
|
||||
|
|
||||
1. Permission handling |
|
||||
2. Camera switching |
|
||||
3. Error conditions |
|
||||
4. Platform compatibility |
|
||||
5. Performance metrics |
|
||||
6. Upload scenarios |
|
||||
7. Image processing |
|
||||
|
|
||||
### Test Environment |
|
||||
|
|
||||
- Multiple browsers |
|
||||
- iOS and Android devices |
|
||||
- Desktop platforms |
|
||||
- Various network conditions |
|
||||
- Different camera configurations |
|
@ -1,156 +0,0 @@ |
|||||
## Build Configuration |
|
||||
|
|
||||
### Common Vite Configuration |
|
||||
```typescript |
|
||||
// vite.config.common.mts |
|
||||
export async function createBuildConfig(mode: string) { |
|
||||
const isCapacitor = mode === "capacitor"; |
|
||||
|
|
||||
return defineConfig({ |
|
||||
build: { |
|
||||
rollupOptions: { |
|
||||
output: { |
|
||||
manualChunks: { |
|
||||
'vue-vendor': ['vue', 'vue-router', 'vue-facing-decorator'] |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
}, |
|
||||
define: { |
|
||||
__USE_QR_READER__: JSON.stringify(!isCapacitor), |
|
||||
__IS_MOBILE__: JSON.stringify(isCapacitor), |
|
||||
}, |
|
||||
optimizeDeps: { |
|
||||
include: [ |
|
||||
'@capacitor-mlkit/barcode-scanning', |
|
||||
'vue-qrcode-reader' |
|
||||
] |
|
||||
}, |
|
||||
resolve: { |
|
||||
alias: { |
|
||||
'@capacitor/app': path.resolve(__dirname, 'node_modules/@capacitor/app') |
|
||||
} |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
### Web-Specific Configuration |
|
||||
```typescript |
|
||||
// vite.config.web.mts |
|
||||
import { defineConfig, mergeConfig } from "vite"; |
|
||||
import { createBuildConfig } from "./vite.config.common.mts"; |
|
||||
|
|
||||
export default defineConfig(async () => { |
|
||||
const baseConfig = await createBuildConfig('web'); |
|
||||
|
|
||||
return mergeConfig(baseConfig, { |
|
||||
define: { |
|
||||
__USE_QR_READER__: true, |
|
||||
__IS_MOBILE__: false, |
|
||||
} |
|
||||
}); |
|
||||
}); |
|
||||
``` |
|
||||
|
|
||||
### Capacitor-Specific Configuration |
|
||||
```typescript |
|
||||
// vite.config.capacitor.mts |
|
||||
import { defineConfig, mergeConfig } from "vite"; |
|
||||
import { createBuildConfig } from "./vite.config.common.mts"; |
|
||||
|
|
||||
export default defineConfig(async () => { |
|
||||
const baseConfig = await createBuildConfig('capacitor'); |
|
||||
|
|
||||
return mergeConfig(baseConfig, { |
|
||||
define: { |
|
||||
__USE_QR_READER__: false, |
|
||||
__IS_MOBILE__: true, |
|
||||
}, |
|
||||
build: { |
|
||||
rollupOptions: { |
|
||||
external: ['vue-qrcode-reader'], // Exclude web QR reader from mobile builds |
|
||||
output: { |
|
||||
entryFileNames: '[name]-mobile.js', |
|
||||
chunkFileNames: '[name]-mobile.js', |
|
||||
assetFileNames: '[name]-mobile.[ext]' |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
}); |
|
||||
}); |
|
||||
``` |
|
||||
|
|
||||
### Build Scripts |
|
||||
Add these scripts to your `package.json`: |
|
||||
```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" |
|
||||
} |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
### Environment Variables |
|
||||
Create a `.env` file: |
|
||||
```bash |
|
||||
# QR Scanner Configuration |
|
||||
VITE_QR_SCANNER_ENABLED=true |
|
||||
VITE_DEFAULT_CAMERA=back |
|
||||
``` |
|
||||
|
|
||||
### Build Process |
|
||||
|
|
||||
1. **Web Build** |
|
||||
```bash |
|
||||
npm run build:web |
|
||||
``` |
|
||||
This will: |
|
||||
- Include vue-qrcode-reader |
|
||||
- Set __USE_QR_READER__ to true |
|
||||
- Set __IS_MOBILE__ to false |
|
||||
- Build for web browsers |
|
||||
|
|
||||
2. **Capacitor Build** |
|
||||
```bash |
|
||||
npm run build:capacitor |
|
||||
``` |
|
||||
This will: |
|
||||
- Exclude vue-qrcode-reader |
|
||||
- Set __USE_QR_READER__ to false |
|
||||
- Set __IS_MOBILE__ to true |
|
||||
- Build for mobile platforms |
|
||||
|
|
||||
3. **Build Both** |
|
||||
```bash |
|
||||
npm run build:all |
|
||||
``` |
|
||||
|
|
||||
### Important Notes |
|
||||
|
|
||||
1. **Dependencies** |
|
||||
- Ensure all QR-related dependencies are properly listed in package.json |
|
||||
- Use exact versions to avoid compatibility issues |
|
||||
- Consider using peer dependencies for shared libraries |
|
||||
|
|
||||
2. **Bundle Size** |
|
||||
- Web build includes vue-qrcode-reader (~100KB) |
|
||||
- Mobile build includes @capacitor-mlkit/barcode-scanning (~50KB) |
|
||||
- Consider using dynamic imports for lazy loading |
|
||||
|
|
||||
3. **Platform Detection** |
|
||||
- Build flags determine which implementation to use |
|
||||
- Runtime checks provide fallback options |
|
||||
- Environment variables can override defaults |
|
||||
|
|
||||
4. **Performance** |
|
||||
- Mobile builds optimize for native performance |
|
||||
- Web builds include necessary polyfills |
|
||||
- Chunk splitting improves load times |
|
||||
|
|
||||
5. **Debugging** |
|
||||
- Source maps are enabled for development |
|
||||
- Build artifacts are properly named for identification |
|
||||
- Console logs help track initialization |
|
@ -0,0 +1,21 @@ |
|||||
|
import { GiveSummaryRecord } from "./records"; |
||||
|
|
||||
|
// Common interface for contact information
|
||||
|
export interface ContactInfo { |
||||
|
known: boolean; |
||||
|
displayName: string; |
||||
|
profileImageUrl?: string; |
||||
|
} |
||||
|
|
||||
|
// Define the contact information fields
|
||||
|
interface GiveContactInfo { |
||||
|
giver: ContactInfo; |
||||
|
issuer: ContactInfo; |
||||
|
receiver: ContactInfo; |
||||
|
providerPlanName?: string; |
||||
|
recipientProjectName?: string; |
||||
|
image?: string; |
||||
|
} |
||||
|
|
||||
|
// Combine GiveSummaryRecord with contact information using intersection type
|
||||
|
export type GiveRecordWithContactInfo = GiveSummaryRecord & GiveContactInfo; |
@ -1,103 +0,0 @@ |
|||||
/** |
|
||||
* @file Deep Link Type Definitions and Validation Schemas |
|
||||
* @author Matthew Raymer |
|
||||
* |
|
||||
* This file defines the type system and validation schemas for deep linking in the TimeSafari app. |
|
||||
* It uses Zod for runtime validation while providing TypeScript types for compile-time checking. |
|
||||
* |
|
||||
* Type Strategy: |
|
||||
* 1. Define base URL schema to validate the fundamental deep link structure |
|
||||
* 2. Define route-specific parameter schemas with exact validation rules |
|
||||
* 3. Generate TypeScript types from Zod schemas for type safety |
|
||||
* 4. Export both schemas and types for use in deep link handling |
|
||||
* |
|
||||
* Usage: |
|
||||
* - Import schemas for runtime validation in deep link handlers |
|
||||
* - Import types for type-safe parameter handling in components |
|
||||
* - Use DeepLinkParams type for type-safe access to route parameters |
|
||||
* |
|
||||
* @example |
|
||||
* // Runtime validation
|
|
||||
* const params = deepLinkSchemas.claim.parse({ id: "123", view: "details" }); |
|
||||
* |
|
||||
* // Type-safe parameter access
|
|
||||
* function handleClaimParams(params: DeepLinkParams["claim"]) { |
|
||||
* // TypeScript knows params.id exists and params.view is optional
|
|
||||
* } |
|
||||
*/ |
|
||||
import { z } from "zod"; |
|
||||
|
|
||||
// Add a union type of all valid route paths
|
|
||||
export const VALID_DEEP_LINK_ROUTES = [ |
|
||||
"user-profile", |
|
||||
"project-details", |
|
||||
"onboard-meeting-setup", |
|
||||
"invite-one-accept", |
|
||||
"contact-import", |
|
||||
"confirm-gift", |
|
||||
"claim", |
|
||||
"claim-cert", |
|
||||
"claim-add-raw", |
|
||||
"contact-edit", |
|
||||
"contacts", |
|
||||
"did", |
|
||||
] as const; |
|
||||
|
|
||||
// Create a type from the array
|
|
||||
export type DeepLinkRoute = (typeof VALID_DEEP_LINK_ROUTES)[number]; |
|
||||
|
|
||||
// Update your schema definitions to use this type
|
|
||||
export const baseUrlSchema = z.object({ |
|
||||
scheme: z.literal("timesafari"), |
|
||||
path: z.string(), |
|
||||
queryParams: z.record(z.string()).optional(), |
|
||||
}); |
|
||||
|
|
||||
// Use the type to ensure route validation
|
|
||||
export const routeSchema = z.enum(VALID_DEEP_LINK_ROUTES); |
|
||||
|
|
||||
// Parameter validation schemas for each route type
|
|
||||
export const deepLinkSchemas = { |
|
||||
"user-profile": z.object({ |
|
||||
id: z.string(), |
|
||||
}), |
|
||||
"project-details": z.object({ |
|
||||
id: z.string(), |
|
||||
}), |
|
||||
"onboard-meeting-setup": z.object({ |
|
||||
id: z.string(), |
|
||||
}), |
|
||||
"invite-one-accept": z.object({ |
|
||||
id: z.string(), |
|
||||
}), |
|
||||
"contact-import": z.object({ |
|
||||
jwt: z.string(), |
|
||||
}), |
|
||||
"confirm-gift": z.object({ |
|
||||
id: z.string(), |
|
||||
}), |
|
||||
claim: z.object({ |
|
||||
id: z.string(), |
|
||||
}), |
|
||||
"claim-cert": z.object({ |
|
||||
id: z.string(), |
|
||||
}), |
|
||||
"claim-add-raw": z.object({ |
|
||||
id: z.string(), |
|
||||
claim: z.string().optional(), |
|
||||
claimJwtId: z.string().optional(), |
|
||||
}), |
|
||||
"contact-edit": z.object({ |
|
||||
did: z.string(), |
|
||||
}), |
|
||||
contacts: z.object({ |
|
||||
contacts: z.string(), // JSON string of contacts array
|
|
||||
}), |
|
||||
did: z.object({ |
|
||||
did: z.string(), |
|
||||
}), |
|
||||
}; |
|
||||
|
|
||||
export type DeepLinkParams = { |
|
||||
[K in keyof typeof deepLinkSchemas]: z.infer<(typeof deepLinkSchemas)[K]>; |
|
||||
}; |
|
@ -1,25 +0,0 @@ |
|||||
import { GiveSummaryRecord, GiveVerifiableCredential } from "../interfaces"; |
|
||||
|
|
||||
export interface GiveRecordWithContactInfo extends GiveSummaryRecord { |
|
||||
jwtId: string; |
|
||||
fullClaim: GiveVerifiableCredential; |
|
||||
giver: { |
|
||||
known: boolean; |
|
||||
displayName: string; |
|
||||
profileImageUrl?: string; |
|
||||
}; |
|
||||
issuer: { |
|
||||
known: boolean; |
|
||||
displayName: string; |
|
||||
profileImageUrl?: string; |
|
||||
}; |
|
||||
receiver: { |
|
||||
known: boolean; |
|
||||
displayName: string; |
|
||||
profileImageUrl?: string; |
|
||||
}; |
|
||||
providerPlanName?: string; |
|
||||
recipientProjectName?: string; |
|
||||
description: string; |
|
||||
image?: string; |
|
||||
} |
|
Loading…
Reference in new issue