Browse Source

Merge branch 'qrcode-reboot' of ssh://173.199.124.46:222/trent_larson/crowd-funder-for-time-pwa into qrcode-reboot

qrcode-reboot
Jose Olarte III 3 days ago
parent
commit
9b73e05bdb
  1. 222
      .cursor/rules/crowd-funder-for-time-pwa/docs/camera-implementation.mdc
  2. 60
      BUILDING.md
  3. 20
      build/entitlements.mac.plist
  4. 308
      docs/camera-implementation.md
  5. 4802
      package-lock.json
  6. 29
      package.json
  7. 85
      src/components/PhotoDialog.vue
  8. 5
      src/views/ContactQRScanView.vue

222
.cursor/rules/crowd-funder-for-time-pwa/docs/camera-implementation.mdc

@ -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

60
BUILDING.md

@ -236,22 +236,76 @@ 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
- **Linux**:
- AppImage: Make executable and run - AppImage: Make executable and run
```bash ```bash
chmod +x dist-electron-packages/TimeSafari-*.AppImage chmod +x dist-electron-packages/TimeSafari-*.AppImage
./dist-electron-packages/TimeSafari-*.AppImage ./dist-electron-packages/TimeSafari-*.AppImage
``` ```
- DEB: Install and run - DEB: Install and run
```bash ```bash
sudo dpkg -i dist-electron-packages/timesafari_*_amd64.deb sudo dpkg -i dist-electron-packages/timesafari_*_amd64.deb
timesafari timesafari
``` ```
- **macOS**:
- `.app` bundle: Double-click `TimeSafari.app` in Finder
- `.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
Note: If you get a security warning when running the app:
1. Right-click the app
2. Select "Open"
3. Click "Open" in the security dialog
### Development Testing ### Development Testing
For testing the Electron build before packaging: For testing the Electron build before packaging:

20
build/entitlements.mac.plist

@ -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

@ -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

4802
package-lock.json

File diff suppressed because it is too large

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"
}
]
}
} }
} }

85
src/components/PhotoDialog.vue

@ -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;

5
src/views/ContactQRScanView.vue

@ -3,7 +3,8 @@
<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
automatically.
</p> </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>
@ -21,6 +22,8 @@
<div class="text-center"> <div class="text-center">
</div> </div>
<div class="text-center"></div>
</section> </section>
</template> </template>

Loading…
Cancel
Save