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 { useSeoMeta } from '@unhead/react';
import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { Layout } from '@/components/Layout'; import { Layout } from '@/components/Layout';
@ -77,7 +77,6 @@ import {
Calendar, Calendar,
MessageCircle, MessageCircle,
} from 'lucide-react'; } from 'lucide-react';
import { CameraCapture } from '@/components/CameraCapture';
import { SpotChat } from '@/components/SpotChat'; import { SpotChat } from '@/components/SpotChat';
import { useUploadFile } from '@/hooks/useUploadFile'; import { useUploadFile } from '@/hooks/useUploadFile';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
@ -605,8 +604,11 @@ export default function MapPage() {
// Mobile layers popover // Mobile layers popover
const [layersOpen, setLayersOpen] = useState(false); const [layersOpen, setLayersOpen] = useState(false);
// Quick camera capture // Quick Snap — uses the native OS camera/gallery picker via a file input.
const [showQuickCamera, setShowQuickCamera] = useState(false); // 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 { mutateAsync: uploadFile, isPending: isUploadingQuickPhoto } = useUploadFile();
const { toast } = useToast(); const { toast } = useToast();
@ -760,13 +762,20 @@ export default function MapPage() {
navigate(`/create-spot?lat=${lat.toFixed(6)}&lng=${lng.toFixed(6)}`); 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 // Handle quick camera capture - upload and navigate to create spot
const handleQuickCameraCapture = async (file: File) => { const handleQuickCameraCapture = async (file: File) => {
try { try {
const tags = await uploadFile(file); const tags = await uploadFile(file);
const url = tags[0]?.[1]; const url = tags[0]?.[1];
if (url) { if (url) {
setShowQuickCamera(false);
// Store the captured image URL in sessionStorage for the create spot page // Store the captured image URL in sessionStorage for the create spot page
sessionStorage.setItem('quickCaptureImage', url); sessionStorage.setItem('quickCaptureImage', url);
// Get current location and navigate to create spot // Get current location and navigate to create spot
@ -1700,9 +1709,9 @@ export default function MapPage() {
</span> </span>
</Link> </Link>
))} ))}
{/* Quick Snap */} {/* Quick Snap — opens the native camera/gallery picker */}
<button <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" className="flex items-center gap-2.5 animate-in fade-in slide-in-from-bottom-2 duration-200"
style={{ animationDelay: '200ms' }} style={{ animationDelay: '200ms' }}
> >
@ -1793,12 +1802,16 @@ export default function MapPage() {
</div> </div>
</div> </div>
{/* Quick Camera Capture Dialog */} {/* Quick Snap — hidden native camera/gallery input.
<CameraCapture `capture="environment"` hints the rear camera on phones; on desktop
open={showQuickCamera} it falls back to a normal file picker. Single photo is all we need. */}
onOpenChange={setShowQuickCamera} <input
onCapture={handleQuickCameraCapture} ref={quickSnapInputRef}
isUploading={isUploadingQuickPhoto} type="file"
accept="image/*"
capture="environment"
className="hidden"
onChange={handleQuickSnapInput}
/> />
{/* Spot Bottom Sheet — slides up when a marker is tapped, map stays visible */} {/* Spot Bottom Sheet — slides up when a marker is tapped, map stays visible */}