fix: sửa lỗi notification và giao diện chat trên android

This commit is contained in:
2026-06-27 11:33:07 +07:00
parent ee42bfcefa
commit fee2aed2e6
68 changed files with 134 additions and 9 deletions
+37 -1
View File
@@ -1,6 +1,7 @@
import React, { useState, useEffect, useRef } from 'react';
import { io, Socket } from 'socket.io-client';
import { Capacitor } from '@capacitor/core';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import { Send, MessageSquare, Loader2, Image as ImageIcon, MapPin, Download, X } from 'lucide-react';
import { useNotification } from '@/hooks/useNotification';
@@ -255,6 +256,41 @@ export const TourChat: React.FC<TourChatProps> = ({ tourId, embedded = false })
});
};
// Handle Native Image Selection (Camera or Library)
const handleNativePhotoSelect = async () => {
try {
const image = await Camera.getPhoto({
quality: 90,
allowEditing: false,
resultType: CameraResultType.Uri,
source: CameraSource.Prompt, // Prompt selection
saveToGallery: true, // Auto-save original photo to device gallery
promptLabelHeader: 'Chọn hình ảnh',
promptLabelPhoto: 'Chọn từ thư viện',
promptLabelPicture: 'Chụp ảnh mới (Camera)'
});
if (image && image.webPath) {
setImagePreview(image.webPath);
// Convert Capacitor webPath resource back to standard File instance
const response = await fetch(image.webPath);
const blob = await response.blob();
const file = new File([blob], `photo.${image.format}`, { type: `image/${image.format}` });
setSelectedImage(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'
});
}
}
};
// Handle Image Selection
const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
@@ -609,7 +645,7 @@ export const TourChat: React.FC<TourChatProps> = ({ tourId, embedded = false })
{/* Attach photo button */}
<button
type="button"
onClick={() => fileInputRef.current?.click()}
onClick={Capacitor.isNativePlatform() ? handleNativePhotoSelect : () => fileInputRef.current?.click()}
className="p-2.5 bg-white border border-gray-200 hover:bg-gray-50 text-gray-500 rounded-xl transition-all shadow-sm active:scale-95 flex items-center justify-center shrink-0"
title="Đính kèm hình ảnh"
>
+20
View File
@@ -1,5 +1,7 @@
import React, { createContext, useContext, useState, useCallback, useEffect } from 'react';
import { NotificationModal } from '../components/NotificationModal';
import { Capacitor } from '@capacitor/core';
import { LocalNotifications } from '@capacitor/local-notifications';
interface NotificationOptions {
title: string;
@@ -24,6 +26,24 @@ export const NotificationProvider: React.FC<{ children: React.ReactNode }> = ({
message,
type,
});
if (Capacitor.isNativePlatform()) {
LocalNotifications.schedule({
notifications: [
{
title: title || 'YoTrip',
body: message,
id: Math.floor(Math.random() * 1000000),
schedule: { at: new Date(Date.now() + 100) },
sound: 'default',
actionTypeId: 'OPEN_APP',
extra: null
}
]
}).catch(err => {
console.error('[LocalNotifications] Error scheduling native notification:', err);
});
}
}, []);
const handleClose = useCallback(() => {
+2 -2
View File
@@ -1906,7 +1906,7 @@ export const TourDetailPage = ({
<div className="min-h-dvh bg-[var(--background)] pb-20 overflow-x-hidden">
{/* Top Navigation Bar */}
<div className="sticky top-0 z-30 bg-[var(--surface)]/80 backdrop-blur-md border-b border-[var(--border)] px-4 py-3 flex items-center justify-between">
<button onClick={onBack} className="p-2 hover:bg-[var(--background)] rounded-full transition-colors">
<button onClick={activeTab === 'chat' ? () => setActiveTab('plan') : onBack} className="p-2 hover:bg-[var(--background)] rounded-full transition-colors">
<ChevronLeft className="w-5 h-5 sm:w-6 sm:h-6 text-[var(--text-secondary)]" />
</button>
<h1 className="text-base sm:text-lg font-bold text-[var(--text-primary)] truncate px-4 flex-1 text-center">
@@ -2206,7 +2206,7 @@ export const TourDetailPage = ({
{/* Main Content Area - Điều chỉnh max-width khi ở chế độ bản đồ */}
<div className={`${activeTab === 'plan' && viewMode === 'map' ? 'w-full !px-0 !pb-0' : 'max-w-2xl mx-auto px-4 pb-24'}`}>
{/* Tab Switcher */}
{!(activeTab === 'plan' && viewMode === 'map') && (
{!(activeTab === 'plan' && viewMode === 'map') && activeTab !== 'chat' && (
<div className="bg-white rounded-2xl shadow-lg border border-gray-100 p-1 flex mb-6 sticky top-[60px] z-40">
{tabs.map((tab) => (
<button
+10
View File
@@ -1,5 +1,6 @@
import { Capacitor } from '@capacitor/core';
import { GoogleAuth } from '@codetrix-studio/capacitor-google-auth';
import { LocalNotifications } from '@capacitor/local-notifications';
function rewriteUrls(obj: any, backendUrl: string): any {
if (obj === null || obj === undefined) return obj;
@@ -37,6 +38,15 @@ if (Capacitor.isNativePlatform()) {
console.error('[Native OAuth] Failed to initialize GoogleAuth client:', e);
}
// Request native local notification permission on startup
try {
LocalNotifications.requestPermissions().then(result => {
console.log('[LocalNotifications] Permissions requested:', result);
});
} catch (e) {
console.error('[LocalNotifications] Failed to request permissions:', e);
}
const backendUrl = import.meta.env.VITE_BACKEND_URL || 'https://yotrip.labz.io.vn';
const originalFetch = window.fetch;