**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:
@@ -1,3 +1,8 @@
|
||||
export interface ImageResult {
|
||||
blob: Blob;
|
||||
fileName: string;
|
||||
}
|
||||
|
||||
export interface PlatformService {
|
||||
// File system operations
|
||||
readFile(path: string): Promise<string>;
|
||||
@@ -6,8 +11,8 @@ export interface PlatformService {
|
||||
listFiles(directory: string): Promise<string[]>;
|
||||
|
||||
// Camera operations
|
||||
takePicture(): Promise<string>;
|
||||
pickImage(): Promise<string>;
|
||||
takePicture(): Promise<ImageResult>;
|
||||
pickImage(): Promise<ImageResult>;
|
||||
|
||||
// Platform specific features
|
||||
isCapacitor(): boolean;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { PlatformService } from '../PlatformService';
|
||||
import { PlatformService } from "../PlatformService";
|
||||
|
||||
export class ElectronPlatformService implements PlatformService {
|
||||
async readFile(path: string): Promise<string> {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
async writeFile(path: string, content: string): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
async deleteFile(path: string): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
async listFiles(directory: string): Promise<string[]> {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
async takePicture(): Promise<string> {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
async pickImage(): Promise<string> {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
isCapacitor(): boolean {
|
||||
@@ -42,6 +42,6 @@ export class ElectronPlatformService implements PlatformService {
|
||||
}
|
||||
|
||||
async handleDeepLink(url: string): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
import { PlatformService } from '../PlatformService';
|
||||
import { PlatformService } from "../PlatformService";
|
||||
|
||||
export class PyWebViewPlatformService implements PlatformService {
|
||||
async readFile(path: string): Promise<string> {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
async writeFile(path: string, content: string): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
async deleteFile(path: string): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
async listFiles(directory: string): Promise<string[]> {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
async takePicture(): Promise<string> {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
async pickImage(): Promise<string> {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
isCapacitor(): boolean {
|
||||
@@ -42,6 +42,6 @@ export class PyWebViewPlatformService implements PlatformService {
|
||||
}
|
||||
|
||||
async handleDeepLink(url: string): Promise<void> {
|
||||
throw new Error('Not implemented');
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PlatformService } from "../PlatformService";
|
||||
import { ImageResult, PlatformService } from "../PlatformService";
|
||||
import { logger } from "../../utils/logger";
|
||||
|
||||
export class WebPlatformService implements PlatformService {
|
||||
async readFile(path: string): Promise<string> {
|
||||
@@ -17,23 +18,28 @@ export class WebPlatformService implements PlatformService {
|
||||
throw new Error("File system access not available in web platform");
|
||||
}
|
||||
|
||||
async takePicture(): Promise<string> {
|
||||
async takePicture(): Promise<ImageResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = "image/*";
|
||||
input.capture = "environment";
|
||||
|
||||
input.onchange = (e) => {
|
||||
input.onchange = async (e) => {
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
resolve(event.target?.result as string);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
try {
|
||||
const blob = await this.processImageFile(file);
|
||||
resolve({
|
||||
blob,
|
||||
fileName: file.name || "photo.jpg"
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error processing camera image:", error);
|
||||
reject(new Error("Failed to process camera image"));
|
||||
}
|
||||
} else {
|
||||
reject(new Error("No file selected"));
|
||||
reject(new Error("No image captured"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -41,22 +47,27 @@ export class WebPlatformService implements PlatformService {
|
||||
});
|
||||
}
|
||||
|
||||
async pickImage(): Promise<string> {
|
||||
async pickImage(): Promise<ImageResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = "image/*";
|
||||
|
||||
input.onchange = (e) => {
|
||||
input.onchange = async (e) => {
|
||||
const file = (e.target as HTMLInputElement).files?.[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
resolve(event.target?.result as string);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
try {
|
||||
const blob = await this.processImageFile(file);
|
||||
resolve({
|
||||
blob,
|
||||
fileName: file.name || "photo.jpg"
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error processing picked image:", error);
|
||||
reject(new Error("Failed to process picked image"));
|
||||
}
|
||||
} else {
|
||||
reject(new Error("No file selected"));
|
||||
reject(new Error("No image selected"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -64,6 +75,28 @@ export class WebPlatformService implements PlatformService {
|
||||
});
|
||||
}
|
||||
|
||||
private async processImageFile(file: File): Promise<Blob> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
const dataUrl = event.target?.result as string;
|
||||
// Convert to blob to ensure consistent format
|
||||
fetch(dataUrl)
|
||||
.then(res => res.blob())
|
||||
.then(blob => resolve(blob))
|
||||
.catch(error => {
|
||||
logger.error("Error converting data URL to blob:", error);
|
||||
reject(error);
|
||||
});
|
||||
};
|
||||
reader.onerror = (error) => {
|
||||
logger.error("Error reading file:", error);
|
||||
reject(error);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
isCapacitor(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user