The previous file-input-only approach fixed the crash but opened the gallery on native because capture="environment" is only a hint the OS can ignore. New platform-aware flow (lib/quickCapture.ts): - Native (APK): open the OS camera directly via the runtime-injected window.Capacitor.Plugins.Camera with source:'CAMERA' — forces the real camera and gives built-in preview/retake. Accessed off the runtime object, so no '@capacitor/camera' specifier in the bundle (keeps the white-screen fix intact). User cancel is treated as a no-op. - Web: falls back to the file input with capture="environment". - If the native camera errors, gracefully falls back to the file input. MapPage Quick Snap now routes through handleQuickSnap() → the captured photo feeds the existing upload → create-spot flow unchanged. Bump 1.0.69. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
70 lines
2.5 KiB
TypeScript
70 lines
2.5 KiB
TypeScript
/**
|
|
* Quick Snap capture helper — platform-aware, camera-first.
|
|
*
|
|
* NATIVE (Capacitor APK): uses the runtime-injected Camera plugin
|
|
* (window.Capacitor.Plugins.Camera) with source:'CAMERA', which forces the
|
|
* real OS camera and ships its own preview + retake UI. We read the plugin
|
|
* off the runtime object rather than importing '@capacitor/camera', so no
|
|
* bare specifier ends up in the bundle (that was the white-screen cause).
|
|
*
|
|
* WEB: there is no reliable way to force the camera from JS, so the caller
|
|
* falls back to a file input with capture="environment" (a hint the browser
|
|
* may honor). This module only handles the native path; isNativeCapture()
|
|
* tells the caller which route to take.
|
|
*/
|
|
|
|
interface CapacitorCameraPlugin {
|
|
getPhoto(options: {
|
|
resultType: string;
|
|
source: string;
|
|
quality: number;
|
|
correctOrientation: boolean;
|
|
}): Promise<{ dataUrl?: string }>;
|
|
}
|
|
|
|
function getNativeCamera(): CapacitorCameraPlugin | null {
|
|
const cap = (window as unknown as {
|
|
Capacitor?: {
|
|
isNativePlatform?: () => boolean;
|
|
Plugins?: { Camera?: CapacitorCameraPlugin };
|
|
};
|
|
}).Capacitor;
|
|
if (!cap?.isNativePlatform?.()) return null;
|
|
return cap.Plugins?.Camera ?? null;
|
|
}
|
|
|
|
/** True when the native Camera plugin is available (i.e. inside the app). */
|
|
export function isNativeCapture(): boolean {
|
|
return getNativeCamera() !== null;
|
|
}
|
|
|
|
/**
|
|
* Open the native camera and return the captured photo as a File.
|
|
* Returns null if the user cancels. Throws if the plugin is unavailable or
|
|
* errors (caller should fall back to the web file input).
|
|
*/
|
|
export async function captureFromNativeCamera(): Promise<File | null> {
|
|
const Camera = getNativeCamera();
|
|
if (!Camera) throw new Error('Native camera unavailable');
|
|
|
|
let photo: { dataUrl?: string };
|
|
try {
|
|
photo = await Camera.getPhoto({
|
|
resultType: 'dataUrl', // CameraResultType.DataUrl
|
|
source: 'CAMERA', // CameraSource.Camera — forces the camera, not the gallery
|
|
quality: 90,
|
|
correctOrientation: true,
|
|
});
|
|
} catch (err) {
|
|
// Capacitor throws with a "cancel" message when the user backs out — treat
|
|
// that as a benign null rather than an error.
|
|
const msg = err instanceof Error ? err.message.toLowerCase() : String(err).toLowerCase();
|
|
if (msg.includes('cancel')) return null;
|
|
throw err;
|
|
}
|
|
|
|
if (!photo.dataUrl) return null;
|
|
const res = await fetch(photo.dataUrl);
|
|
const blob = await res.blob();
|
|
return new File([blob], `skate-spot-${Date.now()}.jpg`, { type: 'image/jpeg' });
|
|
}
|