feat: use Capacitor Camera API to select/capture photos to preserve EXIF location on Android app
This commit is contained in:
Binary file not shown.
Vendored
+4
-4
@@ -21,13 +21,13 @@
|
||||
<meta name="twitter:title" content="Travel Planner - Lên kế hoạch chuyến đi của bạn" />
|
||||
<meta name="twitter:description" content="Khám phá những hành trình tuyệt vời và chia sẻ khoảnh khắc đẹp với cộng đồng du lịch." />
|
||||
<meta name="twitter:image" content="https://yotrip.labz.io.vn/background.avif" />
|
||||
<script type="module" crossorigin src="/assets/index-CRcJJCpn.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-C0KfLnj9.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-CMxvf4Kt.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/vendor-others-C077EAcu.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/vendor-others-CNhtyHGs.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/vendor-editor-BDwQQzB8.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/vendor-maps-1-B38H26.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/vendor-react-DRCYLnn6.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/vendor-pdf-TQOwx6ff.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BF5_05kG.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/vendor-pdf-C3XQY6t9.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/vendor-maps-CcXJxNtP.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/vendor-react-CuSj0z1j.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-w726aohe.css">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Compass, Map as MapIcon, Camera, ShieldAlert, Image as ImageIcon, X } from 'lucide-react';
|
||||
import { Compass, Map as MapIcon, Camera as CameraIcon, ShieldAlert, Image as ImageIcon, X } from 'lucide-react';
|
||||
import { LoginModal } from '../components/LoginModal';
|
||||
import { ReportBusinessModal } from '../components/ReportBusinessModal';
|
||||
import { TagSelectModal } from '../components/TagSelectModal';
|
||||
@@ -15,6 +15,8 @@ import { processImageModeration } from '../hooks/useImageModeration';
|
||||
import { useTranslation } from '../hooks/useTranslation';
|
||||
import { processAndResizeImage } from '../utils/imageProcessor';
|
||||
import { getDeviceLocation } from '../utils/geolocation';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
|
||||
|
||||
interface LandingPageProps {
|
||||
onContinue?: () => void;
|
||||
@@ -188,12 +190,7 @@ export const LandingPage: React.FC<LandingPageProps> = ({
|
||||
return () => clearInterval(interval);
|
||||
}, [publicPhotos]);
|
||||
|
||||
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
notify({ title: 'Đang xử lý...', message: 'Vui lòng chờ trong giây lát.', type: 'info' });
|
||||
|
||||
const processAndUploadFile = async (file: File) => {
|
||||
try {
|
||||
// Process image: Reads EXIF data and forces 2K resizing on device memory
|
||||
const { file: processedFile, latitude: exifLat, longitude: exifLng } = await processAndResizeImage(file);
|
||||
@@ -262,12 +259,50 @@ export const LandingPage: React.FC<LandingPageProps> = ({
|
||||
setIsTagsModalOpen(true);
|
||||
} catch (error: any) {
|
||||
notify({ title: 'Lỗi', message: error.message, type: 'error' });
|
||||
} finally {
|
||||
// Reset input để có thể chọn lại cùng 1 file
|
||||
if (event.target) event.target.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleNativePhotoPick = async (source: CameraSource) => {
|
||||
try {
|
||||
const image = await Camera.getPhoto({
|
||||
quality: 90,
|
||||
allowEditing: false,
|
||||
resultType: CameraResultType.Uri,
|
||||
source: source,
|
||||
saveToGallery: source === CameraSource.Camera // Tự động lưu ảnh gốc vào thư viện nếu chụp bằng Camera
|
||||
});
|
||||
|
||||
if (image && image.webPath) {
|
||||
notify({ title: 'Đang xử lý...', message: 'Vui lòng chờ trong giây lát.', type: 'info' });
|
||||
|
||||
// Convert Capacitor webPath resource back to standard File instance
|
||||
const response = await fetch(image.webPath);
|
||||
const blob = await response.blob();
|
||||
const originalName = `photo-${Date.now()}.${image.format}`;
|
||||
const file = new File([blob], originalName, { type: `image/${image.format}` });
|
||||
|
||||
// Process this file using our standard handler
|
||||
await processAndUploadFile(file);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Lỗi chọn ảnh native:', error);
|
||||
if (error?.message !== 'User cancelled photos app' && error?.message !== 'User cancelled camera') {
|
||||
notify({
|
||||
title: 'Lỗi',
|
||||
message: 'Không thể truy cập máy ảnh hoặc thư viện ảnh.',
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
await processAndUploadFile(file);
|
||||
if (event.target) event.target.value = '';
|
||||
};
|
||||
|
||||
const handleConfirmTags = async (selectedTags: string[]) => {
|
||||
if (!pendingPhotoFile) return;
|
||||
|
||||
@@ -675,7 +710,7 @@ export const LandingPage: React.FC<LandingPageProps> = ({
|
||||
onClick={() => setIsPhotoSourceModalOpen(true)}
|
||||
className="flex-1 flex items-center justify-center gap-2 bg-white/20 backdrop-blur-lg hover:bg-white/30 text-white font-bold py-3.5 rounded-2xl transition-all shadow-2xl border border-white/30 active:scale-95 text-xs sm:text-sm whitespace-nowrap"
|
||||
>
|
||||
<Camera className="w-4.5 h-4.5" />
|
||||
<CameraIcon className="w-4.5 h-4.5" />
|
||||
<span>{t('shortCamera') || 'Chụp ảnh'}</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -748,12 +783,16 @@ export const LandingPage: React.FC<LandingPageProps> = ({
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsPhotoSourceModalOpen(false);
|
||||
cameraInputRef.current?.click();
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
handleNativePhotoPick(CameraSource.Camera);
|
||||
} else {
|
||||
cameraInputRef.current?.click();
|
||||
}
|
||||
}}
|
||||
className="w-full flex items-center gap-4 p-4 bg-[var(--background)] hover:bg-[var(--background-alt)] border border-[var(--border)] rounded-2xl transition-all active:scale-95"
|
||||
>
|
||||
<div className="flex-shrink-0 p-3 bg-blue-100 rounded-full">
|
||||
<Camera className="w-6 h-6 text-blue-600" />
|
||||
<CameraIcon className="w-6 h-6 text-blue-600" />
|
||||
</div>
|
||||
<div className="flex-1 text-left">
|
||||
<div className="font-bold text-[var(--text-primary)]">Chụp ảnh bằng camera</div>
|
||||
@@ -765,7 +804,11 @@ export const LandingPage: React.FC<LandingPageProps> = ({
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsPhotoSourceModalOpen(false);
|
||||
galleryInputRef.current?.click();
|
||||
if (Capacitor.isNativePlatform()) {
|
||||
handleNativePhotoPick(CameraSource.Photos);
|
||||
} else {
|
||||
galleryInputRef.current?.click();
|
||||
}
|
||||
}}
|
||||
className="w-full flex items-center gap-4 p-4 bg-[var(--background)] hover:bg-[var(--background-alt)] border border-[var(--border)] rounded-2xl transition-all active:scale-95"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user