forked from jsnbuchanan/crowd-funder-for-time-pwa
Merge branch 'qrcode-reboot' of ssh://173.199.124.46:222/trent_larson/crowd-funder-for-time-pwa into qrcode-reboot
This commit is contained in:
@@ -0,0 +1,222 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
# Camera Implementation Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document describes how camera functionality is implemented across the TimeSafari application. The application uses cameras for two main purposes:
|
||||||
|
|
||||||
|
1. QR Code scanning
|
||||||
|
2. Photo capture
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
## 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
|
||||||
|
- Falls back to file selection if camera unavailable
|
||||||
|
- Processes captured images for consistent format
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
|
||||||
|
#### ElectronPlatformService
|
||||||
|
|
||||||
|
Desktop implementation (currently unimplemented).
|
||||||
|
|
||||||
|
**Status:**
|
||||||
|
|
||||||
|
- Camera functionality not yet implemented
|
||||||
|
- Planned to use Electron's media APIs
|
||||||
|
|
||||||
|
## Platform-Specific Considerations
|
||||||
|
|
||||||
|
### iOS
|
||||||
|
|
||||||
|
- Requires `NSCameraUsageDescription` in Info.plist
|
||||||
|
- Supports both front and back cameras
|
||||||
|
- Implements proper permission handling
|
||||||
|
|
||||||
|
### Android
|
||||||
|
|
||||||
|
- Requires camera permissions in manifest
|
||||||
|
- Supports both front and back cameras
|
||||||
|
- Handles permission requests through Capacitor
|
||||||
|
|
||||||
|
### Web
|
||||||
|
|
||||||
|
- Requires HTTPS for camera access
|
||||||
|
- Implements fallback mechanisms
|
||||||
|
- Handles browser compatibility issues
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
### Error Response
|
||||||
|
|
||||||
|
- User-friendly error messages
|
||||||
|
- Troubleshooting tips
|
||||||
|
- Clear instructions for resolution
|
||||||
|
- Platform-specific guidance
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### Permission Management
|
||||||
|
|
||||||
|
- Explicit permission requests
|
||||||
|
- Permission state tracking
|
||||||
|
- Graceful handling of denied permissions
|
||||||
|
|
||||||
|
### Data Handling
|
||||||
|
|
||||||
|
- Secure image processing
|
||||||
|
- Proper cleanup of camera resources
|
||||||
|
- No persistent storage of camera data
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
### User Experience
|
||||||
|
|
||||||
|
1. Clear status indicators
|
||||||
|
2. Intuitive camera controls
|
||||||
|
3. Helpful error messages
|
||||||
|
4. Smooth camera switching
|
||||||
|
5. Responsive UI feedback
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
|
||||||
|
1. Electron camera implementation pending
|
||||||
|
2. Some browser compatibility limitations
|
||||||
|
3. Platform-specific quirks to address
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
### Key Packages
|
||||||
|
|
||||||
|
- `@capacitor-mlkit/barcode-scanning`
|
||||||
|
- `qrcode-stream`
|
||||||
|
- `vue-picture-cropper`
|
||||||
|
- Platform-specific camera APIs
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Test Scenarios
|
||||||
|
|
||||||
|
1. Permission handling
|
||||||
|
2. Camera switching
|
||||||
|
3. Error conditions
|
||||||
|
4. Platform compatibility
|
||||||
|
5. Performance metrics
|
||||||
|
|
||||||
|
### Test Environment
|
||||||
|
|
||||||
|
- Multiple browsers
|
||||||
|
- iOS and Android devices
|
||||||
|
- Desktop platforms
|
||||||
|
- Various network conditions
|
||||||
76
BUILDING.md
76
BUILDING.md
@@ -236,21 +236,75 @@ docker run -d \
|
|||||||
- AppImage: `dist-electron-packages/TimeSafari-x.x.x.AppImage`
|
- AppImage: `dist-electron-packages/TimeSafari-x.x.x.AppImage`
|
||||||
- DEB: `dist-electron-packages/timesafari_x.x.x_amd64.deb`
|
- DEB: `dist-electron-packages/timesafari_x.x.x_amd64.deb`
|
||||||
|
|
||||||
|
### macOS Build
|
||||||
|
|
||||||
|
1. Build the electron app in production mode:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build:electron-prod
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Package the Electron app for macOS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# For Intel Macs
|
||||||
|
npm run electron:build-mac
|
||||||
|
|
||||||
|
# For Universal build (Intel + Apple Silicon)
|
||||||
|
npm run electron:build-mac-universal
|
||||||
|
```
|
||||||
|
|
||||||
|
3. The packaged applications will be in `dist-electron-packages/`:
|
||||||
|
- `.app` bundle: `TimeSafari.app`
|
||||||
|
- `.dmg` installer: `TimeSafari-x.x.x.dmg`
|
||||||
|
- `.zip` archive: `TimeSafari-x.x.x-mac.zip`
|
||||||
|
|
||||||
|
### Code Signing and Notarization (macOS)
|
||||||
|
|
||||||
|
For public distribution on macOS, you need to code sign and notarize your app:
|
||||||
|
|
||||||
|
1. Set up environment variables:
|
||||||
|
```bash
|
||||||
|
export CSC_LINK=/path/to/your/certificate.p12
|
||||||
|
export CSC_KEY_PASSWORD=your_certificate_password
|
||||||
|
export APPLE_ID=your_apple_id
|
||||||
|
export APPLE_ID_PASSWORD=your_app_specific_password
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Build with signing:
|
||||||
|
```bash
|
||||||
|
npm run electron:build-mac
|
||||||
|
```
|
||||||
|
|
||||||
### Running the Packaged App
|
### Running the Packaged App
|
||||||
|
|
||||||
- AppImage: Make executable and run
|
- **Linux**:
|
||||||
|
- AppImage: Make executable and run
|
||||||
|
```bash
|
||||||
|
chmod +x dist-electron-packages/TimeSafari-*.AppImage
|
||||||
|
./dist-electron-packages/TimeSafari-*.AppImage
|
||||||
|
```
|
||||||
|
- DEB: Install and run
|
||||||
|
```bash
|
||||||
|
sudo dpkg -i dist-electron-packages/timesafari_*_amd64.deb
|
||||||
|
timesafari
|
||||||
|
```
|
||||||
|
|
||||||
```bash
|
- **macOS**:
|
||||||
chmod +x dist-electron-packages/TimeSafari-*.AppImage
|
- `.app` bundle: Double-click `TimeSafari.app` in Finder
|
||||||
./dist-electron-packages/TimeSafari-*.AppImage
|
- `.dmg` installer:
|
||||||
```
|
1. Double-click the `.dmg` file
|
||||||
|
2. Drag the app to your Applications folder
|
||||||
|
3. Launch from Applications
|
||||||
|
- `.zip` archive:
|
||||||
|
1. Extract the `.zip` file
|
||||||
|
2. Move `TimeSafari.app` to your Applications folder
|
||||||
|
3. Launch from Applications
|
||||||
|
|
||||||
- DEB: Install and run
|
Note: If you get a security warning when running the app:
|
||||||
|
1. Right-click the app
|
||||||
```bash
|
2. Select "Open"
|
||||||
sudo dpkg -i dist-electron-packages/timesafari_*_amd64.deb
|
3. Click "Open" in the security dialog
|
||||||
timesafari
|
|
||||||
```
|
|
||||||
|
|
||||||
### Development Testing
|
### Development Testing
|
||||||
|
|
||||||
|
|||||||
20
build/entitlements.mac.plist
Normal file
20
build/entitlements.mac.plist
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.debugger</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.device.audio-input</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.device.camera</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.personal-information.addressbook</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.personal-information.calendars</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
308
docs/camera-implementation.md
Normal file
308
docs/camera-implementation.md
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
## 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
|
||||||
|
- Falls back to file selection if camera unavailable
|
||||||
|
- Processes captured images for consistent format
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
|
||||||
|
#### ElectronPlatformService
|
||||||
|
|
||||||
|
Desktop implementation (currently unimplemented).
|
||||||
|
|
||||||
|
**Status:**
|
||||||
|
|
||||||
|
- Camera functionality not yet implemented
|
||||||
|
- Planned to use Electron's media APIs
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
### Image Upload and Processing
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
- Secure server upload
|
||||||
|
- Authentication handling
|
||||||
|
- Image format standardization
|
||||||
|
- Error handling and retry
|
||||||
|
- Progress feedback
|
||||||
|
|
||||||
|
**Flow:**
|
||||||
|
1. Image is prepared for upload
|
||||||
|
2. Authentication token is obtained
|
||||||
|
3. Multipart form data is created
|
||||||
|
4. Upload progress is monitored
|
||||||
|
5. Server URL is returned
|
||||||
|
|
||||||
|
## Platform-Specific Considerations
|
||||||
|
|
||||||
|
### iOS
|
||||||
|
|
||||||
|
- Requires `NSCameraUsageDescription` in Info.plist
|
||||||
|
- Supports both front and back cameras
|
||||||
|
- Implements proper permission handling
|
||||||
|
|
||||||
|
### Android
|
||||||
|
|
||||||
|
- Requires camera permissions in manifest
|
||||||
|
- Supports both front and back cameras
|
||||||
|
- Handles permission requests through Capacitor
|
||||||
|
|
||||||
|
### Web
|
||||||
|
|
||||||
|
- Requires HTTPS for camera access
|
||||||
|
- Implements fallback mechanisms
|
||||||
|
- Handles browser compatibility issues
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
### Error Response
|
||||||
|
|
||||||
|
- User-friendly error messages
|
||||||
|
- Troubleshooting tips
|
||||||
|
- Clear instructions for resolution
|
||||||
|
- Platform-specific guidance
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### Permission Management
|
||||||
|
|
||||||
|
- Explicit permission requests
|
||||||
|
- Permission state tracking
|
||||||
|
- Graceful handling of denied permissions
|
||||||
|
|
||||||
|
### Data Handling
|
||||||
|
|
||||||
|
- Secure image processing
|
||||||
|
- Proper cleanup of camera resources
|
||||||
|
- No persistent storage of camera data
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
### User Experience
|
||||||
|
|
||||||
|
1. Clear status indicators
|
||||||
|
2. Intuitive camera controls
|
||||||
|
3. Helpful error messages
|
||||||
|
4. Smooth camera switching
|
||||||
|
5. Responsive UI feedback
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
|
||||||
|
1. Electron camera implementation pending
|
||||||
|
2. Some browser compatibility limitations
|
||||||
|
3. Platform-specific quirks to address
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
### Key Packages
|
||||||
|
|
||||||
|
- `@capacitor-mlkit/barcode-scanning`
|
||||||
|
- `qrcode-stream`
|
||||||
|
- `vue-picture-cropper`
|
||||||
|
- Platform-specific camera APIs
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Test Scenarios
|
||||||
|
|
||||||
|
1. Permission handling
|
||||||
|
2. Camera switching
|
||||||
|
3. Error conditions
|
||||||
|
4. Platform compatibility
|
||||||
|
5. Performance metrics
|
||||||
|
|
||||||
|
### Test Environment
|
||||||
|
|
||||||
|
- Multiple browsers
|
||||||
|
- iOS and Android devices
|
||||||
|
- Desktop platforms
|
||||||
|
- Various network conditions
|
||||||
4634
package-lock.json
generated
4634
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
@@ -41,7 +41,9 @@
|
|||||||
"fastlane:ios:beta": "cd ios && fastlane beta",
|
"fastlane:ios:beta": "cd ios && fastlane beta",
|
||||||
"fastlane:ios:release": "cd ios && fastlane release",
|
"fastlane:ios:release": "cd ios && fastlane release",
|
||||||
"fastlane:android:beta": "cd android && fastlane beta",
|
"fastlane:android:beta": "cd android && fastlane beta",
|
||||||
"fastlane:android:release": "cd android && fastlane release"
|
"fastlane:android:release": "cd android && fastlane release",
|
||||||
|
"electron:build-mac": "npm run build:electron-prod && electron-builder --mac",
|
||||||
|
"electron:build-mac-universal": "npm run build:electron-prod && electron-builder --mac --universal"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor-mlkit/barcode-scanning": "^6.0.0",
|
"@capacitor-mlkit/barcode-scanning": "^6.0.0",
|
||||||
@@ -185,6 +187,29 @@
|
|||||||
"category": "Office",
|
"category": "Office",
|
||||||
"icon": "build/icon.png"
|
"icon": "build/icon.png"
|
||||||
},
|
},
|
||||||
"asar": true
|
"asar": true,
|
||||||
|
"mac": {
|
||||||
|
"target": ["dmg", "zip"],
|
||||||
|
"category": "public.app-category.productivity",
|
||||||
|
"icon": "build/icon.png",
|
||||||
|
"hardenedRuntime": true,
|
||||||
|
"gatekeeperAssess": false,
|
||||||
|
"entitlements": "build/entitlements.mac.plist",
|
||||||
|
"entitlementsInherit": "build/entitlements.mac.plist"
|
||||||
|
},
|
||||||
|
"dmg": {
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"x": 130,
|
||||||
|
"y": 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 410,
|
||||||
|
"y": 220,
|
||||||
|
"type": "link",
|
||||||
|
"path": "/Applications"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -441,8 +441,8 @@ const configureIosProject = async (log) => {
|
|||||||
try {
|
try {
|
||||||
// Try to run pod install normally first
|
// Try to run pod install normally first
|
||||||
log('🔄 Running "pod install" in ios/App directory...');
|
log('🔄 Running "pod install" in ios/App directory...');
|
||||||
execSync('cd ios/App && pod install', { stdio: 'inherit' });
|
execSync('cd ios/App && pod install', { stdio: 'inherit' });
|
||||||
log('✅ CocoaPods installation completed');
|
log('✅ CocoaPods installation completed');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// If that fails, provide detailed instructions
|
// If that fails, provide detailed instructions
|
||||||
log(`⚠️ CocoaPods installation failed: ${error.message}`);
|
log(`⚠️ CocoaPods installation failed: ${error.message}`);
|
||||||
@@ -623,28 +623,28 @@ const runDeeplinkTests = async (log) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Now we can safely create the deeplink tests knowing we have valid data
|
// Now we can safely create the deeplink tests knowing we have valid data
|
||||||
const deeplinkTests = [
|
const deeplinkTests = [
|
||||||
{
|
{
|
||||||
url: `timesafari://claim/${testEnv.CLAIM_ID}`,
|
url: `timesafari://claim/${testEnv.CLAIM_ID}`,
|
||||||
description: 'Claim view'
|
description: 'Claim view'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `timesafari://claim-cert/${testEnv.CERT_ID || testEnv.CLAIM_ID}`,
|
url: `timesafari://claim-cert/${testEnv.CERT_ID || testEnv.CLAIM_ID}`,
|
||||||
description: 'Claim certificate view'
|
description: 'Claim certificate view'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `timesafari://claim-add-raw/${testEnv.RAW_CLAIM_ID || testEnv.CLAIM_ID}`,
|
url: `timesafari://claim-add-raw/${testEnv.RAW_CLAIM_ID || testEnv.CLAIM_ID}`,
|
||||||
description: 'Raw claim addition'
|
description: 'Raw claim addition'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: 'timesafari://did/test',
|
url: 'timesafari://did/test',
|
||||||
description: 'DID view with test identifier'
|
description: 'DID view with test identifier'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `timesafari://did/${testEnv.CONTACT1_DID}`,
|
url: `timesafari://did/${testEnv.CONTACT1_DID}`,
|
||||||
description: 'DID view with contact DID'
|
description: 'DID view with contact DID'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: (() => {
|
url: (() => {
|
||||||
if (!testEnv?.CONTACT1_DID) {
|
if (!testEnv?.CONTACT1_DID) {
|
||||||
throw new Error('Cannot construct contact-edit URL: CONTACT1_DID is missing');
|
throw new Error('Cannot construct contact-edit URL: CONTACT1_DID is missing');
|
||||||
@@ -653,13 +653,13 @@ const runDeeplinkTests = async (log) => {
|
|||||||
log('Created contact-edit URL:', url);
|
log('Created contact-edit URL:', url);
|
||||||
return url;
|
return url;
|
||||||
})(),
|
})(),
|
||||||
description: 'Contact editing'
|
description: 'Contact editing'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url: `timesafari://contacts/import?contacts=${encodeURIComponent(JSON.stringify(contacts))}`,
|
url: `timesafari://contacts/import?contacts=${encodeURIComponent(JSON.stringify(contacts))}`,
|
||||||
description: 'Contacts import'
|
description: 'Contacts import'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// Log the final test configuration
|
// Log the final test configuration
|
||||||
log('\n5. Final Test Configuration:');
|
log('\n5. Final Test Configuration:');
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
/** * PhotoDialog.vue - Cross-platform photo capture and selection component * *
|
||||||
|
This component provides a unified interface for taking photos and selecting
|
||||||
|
images * across different platforms (web, mobile) using the PlatformService. It
|
||||||
|
supports: * - Taking photos using device camera * - Selecting images from device
|
||||||
|
gallery * - Image cropping functionality * - Image upload to server * - Error
|
||||||
|
handling and user feedback * * Features: * - Responsive design with mobile-first
|
||||||
|
approach * - Cross-platform compatibility through PlatformService * - Image
|
||||||
|
cropping with aspect ratio control * - Progress feedback during upload * -
|
||||||
|
Comprehensive error handling * * @author Matthew Raymer * @version 1.0.0 * @file
|
||||||
|
PhotoDialog.vue */
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="visible" class="dialog-overlay z-[60]">
|
<div v-if="visible" class="dialog-overlay z-[60]">
|
||||||
<div class="dialog relative">
|
<div class="dialog relative">
|
||||||
@@ -90,16 +101,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
/**
|
|
||||||
* PhotoDialog.vue - Cross-platform photo capture and selection component
|
|
||||||
*
|
|
||||||
* This component provides a unified interface for taking photos and selecting images
|
|
||||||
* across different platforms using the PlatformService.
|
|
||||||
*
|
|
||||||
* @author Matthew Raymer
|
|
||||||
* @file PhotoDialog.vue
|
|
||||||
*/
|
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { Component, Vue } from "vue-facing-decorator";
|
import { Component, Vue } from "vue-facing-decorator";
|
||||||
import VuePictureCropper, { cropper } from "vue-picture-cropper";
|
import VuePictureCropper, { cropper } from "vue-picture-cropper";
|
||||||
@@ -113,37 +114,69 @@ import { PlatformServiceFactory } from "../services/PlatformServiceFactory";
|
|||||||
export default class PhotoDialog extends Vue {
|
export default class PhotoDialog extends Vue {
|
||||||
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
$notify!: (notification: NotificationIface, timeout?: number) => void;
|
||||||
|
|
||||||
|
/** Active DID for user authentication */
|
||||||
activeDid = "";
|
activeDid = "";
|
||||||
|
|
||||||
|
/** Current image blob being processed */
|
||||||
blob?: Blob;
|
blob?: Blob;
|
||||||
|
|
||||||
|
/** Type of claim for the image */
|
||||||
claimType = "";
|
claimType = "";
|
||||||
|
|
||||||
|
/** Whether to show cropping interface */
|
||||||
crop = false;
|
crop = false;
|
||||||
|
|
||||||
|
/** Name of the selected file */
|
||||||
fileName?: string;
|
fileName?: string;
|
||||||
|
|
||||||
|
/** Callback function to set image URL after upload */
|
||||||
setImageCallback: (arg: string) => void = () => {};
|
setImageCallback: (arg: string) => void = () => {};
|
||||||
|
|
||||||
|
/** Whether to show retry button */
|
||||||
showRetry = true;
|
showRetry = true;
|
||||||
|
|
||||||
|
/** Upload progress state */
|
||||||
uploading = false;
|
uploading = false;
|
||||||
|
|
||||||
|
/** Dialog visibility state */
|
||||||
visible = false;
|
visible = false;
|
||||||
|
|
||||||
private platformService = PlatformServiceFactory.getInstance();
|
private platformService = PlatformServiceFactory.getInstance();
|
||||||
URL = window.URL || window.webkitURL;
|
URL = window.URL || window.webkitURL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lifecycle hook: Initializes component and retrieves user settings
|
||||||
|
* @throws {Error} When settings retrieval fails
|
||||||
|
*/
|
||||||
async mounted() {
|
async mounted() {
|
||||||
try {
|
try {
|
||||||
const settings = await retrieveSettingsForActiveAccount();
|
const settings = await retrieveSettingsForActiveAccount();
|
||||||
this.activeDid = settings.activeDid || "";
|
this.activeDid = settings.activeDid || "";
|
||||||
} catch (err: unknown) {
|
} catch (error: unknown) {
|
||||||
logger.error("Error retrieving settings from database:", err);
|
logger.error("Error retrieving settings from database:", error);
|
||||||
this.$notify(
|
this.$notify(
|
||||||
{
|
{
|
||||||
group: "alert",
|
group: "alert",
|
||||||
type: "danger",
|
type: "danger",
|
||||||
title: "Error",
|
title: "Error",
|
||||||
text: err.message || "There was an error retrieving your settings.",
|
text:
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "There was an error retrieving your settings.",
|
||||||
},
|
},
|
||||||
-1,
|
-1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the photo dialog with specified configuration
|
||||||
|
* @param setImageFn - Callback function to handle image URL after upload
|
||||||
|
* @param claimType - Type of claim for the image
|
||||||
|
* @param crop - Whether to enable cropping
|
||||||
|
* @param blob - Optional existing image blob
|
||||||
|
* @param inputFileName - Optional filename for the image
|
||||||
|
*/
|
||||||
open(
|
open(
|
||||||
setImageFn: (arg: string) => void,
|
setImageFn: (arg: string) => void,
|
||||||
claimType: string,
|
claimType: string,
|
||||||
@@ -170,6 +203,9 @@ export default class PhotoDialog extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the photo dialog and resets state
|
||||||
|
*/
|
||||||
close() {
|
close() {
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
const bottomNav = document.querySelector("#QuickNav") as HTMLElement;
|
const bottomNav = document.querySelector("#QuickNav") as HTMLElement;
|
||||||
@@ -179,6 +215,10 @@ export default class PhotoDialog extends Vue {
|
|||||||
this.blob = undefined;
|
this.blob = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Captures a photo using device camera
|
||||||
|
* @throws {Error} When camera access fails
|
||||||
|
*/
|
||||||
async takePhoto() {
|
async takePhoto() {
|
||||||
try {
|
try {
|
||||||
const result = await this.platformService.takePicture();
|
const result = await this.platformService.takePicture();
|
||||||
@@ -198,6 +238,10 @@ export default class PhotoDialog extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects an image from device gallery
|
||||||
|
* @throws {Error} When gallery access fails
|
||||||
|
*/
|
||||||
async pickPhoto() {
|
async pickPhoto() {
|
||||||
try {
|
try {
|
||||||
const result = await this.platformService.pickImage();
|
const result = await this.platformService.pickImage();
|
||||||
@@ -217,14 +261,27 @@ export default class PhotoDialog extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a blob URL for image preview
|
||||||
|
* @param blob - Image blob to create URL for
|
||||||
|
* @returns {string} Blob URL for the image
|
||||||
|
*/
|
||||||
private createBlobURL(blob: Blob): string {
|
private createBlobURL(blob: Blob): string {
|
||||||
return URL.createObjectURL(blob);
|
return URL.createObjectURL(blob);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the current image selection
|
||||||
|
*/
|
||||||
async retryImage() {
|
async retryImage() {
|
||||||
this.blob = undefined;
|
this.blob = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads the current image to the server
|
||||||
|
* Handles cropping if enabled and manages upload state
|
||||||
|
* @throws {Error} When upload fails or server returns error
|
||||||
|
*/
|
||||||
async uploadImage() {
|
async uploadImage() {
|
||||||
this.uploading = true;
|
this.uploading = true;
|
||||||
|
|
||||||
@@ -339,6 +396,7 @@ export default class PhotoDialog extends Vue {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
/* Dialog overlay styling */
|
||||||
.dialog-overlay {
|
.dialog-overlay {
|
||||||
z-index: 60;
|
z-index: 60;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -353,6 +411,7 @@ export default class PhotoDialog extends Vue {
|
|||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dialog container styling */
|
||||||
.dialog {
|
.dialog {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- CONTENT -->
|
<!-- CONTENT -->
|
||||||
<section id="Content" class="relativew-[100vw] h-[100vh]">
|
<section id="Content" class="relativew-[100vw] h-[100vh]">
|
||||||
<div class="absolute inset-x-0 bottom-0 bg-black/50 p-6">
|
<div class="absolute inset-x-0 bottom-0 bg-black/50 p-6">
|
||||||
<p class="text-center text-white mb-3">
|
<p class="text-center text-white mb-3">
|
||||||
Point your camera at a TimeSafari contact QR code to scan it automatically.
|
Point your camera at a TimeSafari contact QR code to scan it
|
||||||
</p>
|
automatically.
|
||||||
|
</p>
|
||||||
|
|
||||||
<p v-if="error" class="text-center text-rose-300 mb-3">{{ error }}</p>
|
<p v-if="error" class="text-center text-rose-300 mb-3">{{ error }}</p>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<button
|
<button
|
||||||
@@ -21,6 +22,8 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center"></div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user