Quick Snap: use native camera/gallery input instead of custom camera

The custom in-app CameraCapture failed on the native APK ("Could not open
the camera") due to Capacitor camera permission plumbing, and a single
photo is all a spot snap needs. Replace the Quick Snap flow on the map with
a hidden native file input (accept="image/*" capture="environment"): one
tap opens the OS camera/gallery, the chosen photo feeds the existing upload
→ create-spot flow unchanged. Works identically on web and native, no
plugin/permission fragility. Removes the CameraCapture usage + import from
MapPage (component still used by CreateSpotPage).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-07-24 19:13:49 +07:00
parent cc2e35f569
commit 87c710bf4a

View file

@ -1,4 +1,4 @@
import { useState, useMemo, useEffect, lazy, Suspense } from 'react';
import { useState, useMemo, useEffect, useRef, lazy, Suspense } from 'react';
import { useSeoMeta } from '@unhead/react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { Layout } from '@/components/Layout';
@ -77,7 +77,6 @@ import {
Calendar,
MessageCircle,
} from 'lucide-react';
import { CameraCapture } from '@/components/CameraCapture';
import { SpotChat } from '@/components/SpotChat';
import { useUploadFile } from '@/hooks/useUploadFile';
import { useToast } from '@/hooks/useToast';
@ -605,8 +604,11 @@ export default function MapPage() {
// Mobile layers popover
const [layersOpen, setLayersOpen] = useState(false);
// Quick camera capture
const [showQuickCamera, setShowQuickCamera] = useState(false);
// Quick Snap — uses the native OS camera/gallery picker via a file input.
// This is more robust than a custom in-app camera (no Capacitor camera
// permission plumbing) and works identically on web and native. A single
// photo is all a spot snap needs.
const quickSnapInputRef = useRef<HTMLInputElement>(null);
const { mutateAsync: uploadFile, isPending: isUploadingQuickPhoto } = useUploadFile();
const { toast } = useToast();
@ -760,13 +762,20 @@ export default function MapPage() {
navigate(`/create-spot?lat=${lat.toFixed(6)}&lng=${lng.toFixed(6)}`);
};
// Native file-input change → hand the chosen photo to the upload flow.
const handleQuickSnapInput = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
// Reset the input so picking the same file again still fires onChange.
e.target.value = '';
if (file) handleQuickCameraCapture(file);
};
// Handle quick camera capture - upload and navigate to create spot
const handleQuickCameraCapture = async (file: File) => {
try {
const tags = await uploadFile(file);
const url = tags[0]?.[1];
if (url) {
setShowQuickCamera(false);
// Store the captured image URL in sessionStorage for the create spot page
sessionStorage.setItem('quickCaptureImage', url);
// Get current location and navigate to create spot
@ -1700,9 +1709,9 @@ export default function MapPage() {
</span>
</Link>
))}
{/* Quick Snap */}
{/* Quick Snap — opens the native camera/gallery picker */}
<button
onClick={() => { setAddDialOpen(false); setShowQuickCamera(true); }}
onClick={() => { setAddDialOpen(false); quickSnapInputRef.current?.click(); }}
className="flex items-center gap-2.5 animate-in fade-in slide-in-from-bottom-2 duration-200"
style={{ animationDelay: '200ms' }}
>
@ -1793,12 +1802,16 @@ export default function MapPage() {
</div>
</div>
{/* Quick Camera Capture Dialog */}
<CameraCapture
open={showQuickCamera}
onOpenChange={setShowQuickCamera}
onCapture={handleQuickCameraCapture}
isUploading={isUploadingQuickPhoto}
{/* Quick Snap — hidden native camera/gallery input.
`capture="environment"` hints the rear camera on phones; on desktop
it falls back to a normal file picker. Single photo is all we need. */}
<input
ref={quickSnapInputRef}
type="file"
accept="image/*"
capture="environment"
className="hidden"
onChange={handleQuickSnapInput}
/>
{/* Spot Bottom Sheet — slides up when a marker is tapped, map stays visible */}