**refactor(PhotoDialog, PlatformService): Implement cross-platform photo capture and encapsulated image processing**

- Replace direct camera library with platform-agnostic `PlatformService`
- Move platform-specific image processing logic to respective platform implementations
- Introduce `ImageResult` interface for consistent image handling across platforms
- Add support for native camera and image picker across all platforms
- Simplify `PhotoDialog` by removing platform-specific logic
- Maintain existing cropping and upload functionality
- Improve error handling and logging throughout
- Clean up UI for better user experience
- Add comprehensive documentation for usage and architecture

**BREAKING CHANGE:** Removes direct camera library dependency in favor of `PlatformService`

This change improves separation of concerns, enhances maintainability, and standardizes cross-platform image handling.
This commit is contained in:
Matthew Raymer
2025-04-06 13:04:26 +00:00
parent abf18835f6
commit 2c84bb50b3
8 changed files with 187 additions and 250 deletions

View File

@@ -1,8 +1,9 @@
import { PlatformService } from "../PlatformService";
import { ImageResult, PlatformService } from "../PlatformService";
import { Capacitor } from "@capacitor/core";
import { Filesystem, Directory } from "@capacitor/filesystem";
import { Camera, CameraResultType, CameraSource } from "@capacitor/camera";
import { App } from "@capacitor/app";
import { logger } from "../../utils/logger";
export class CapacitorPlatformService implements PlatformService {
async readFile(path: string): Promise<string> {
@@ -36,24 +37,64 @@ export class CapacitorPlatformService implements PlatformService {
return result.files;
}
async takePicture(): Promise<string> {
const image = await Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
});
return image.webPath || "";
async takePicture(): Promise<ImageResult> {
try {
const image = await Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Base64,
source: CameraSource.Camera,
});
const blob = await this.processImageData(image.base64String);
return {
blob,
fileName: `photo_${Date.now()}.${image.format || 'jpg'}`
};
} catch (error) {
logger.error("Error taking picture with Capacitor:", error);
throw new Error("Failed to take picture");
}
}
async pickImage(): Promise<string> {
const image = await Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Uri,
source: CameraSource.Photos,
});
return image.webPath || "";
async pickImage(): Promise<ImageResult> {
try {
const image = await Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Base64,
source: CameraSource.Photos,
});
const blob = await this.processImageData(image.base64String);
return {
blob,
fileName: `photo_${Date.now()}.${image.format || 'jpg'}`
};
} catch (error) {
logger.error("Error picking image with Capacitor:", error);
throw new Error("Failed to pick image");
}
}
private async processImageData(base64String?: string): Promise<Blob> {
if (!base64String) {
throw new Error("No image data received");
}
// Convert base64 to blob
const byteCharacters = atob(base64String);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += 512) {
const slice = byteCharacters.slice(offset, offset + 512);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
return new Blob(byteArrays, { type: 'image/jpeg' });
}
isCapacitor(): boolean {