chore: fix notif
This commit is contained in:
+2
-14
@@ -17,7 +17,6 @@
|
||||
"bundleIdentifier": "com.uberstup.clientpanel"
|
||||
},
|
||||
"android": {
|
||||
"googleServicesFile": "./google-services.json",
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/icon.png",
|
||||
"backgroundColor": "#000000"
|
||||
@@ -27,9 +26,7 @@
|
||||
"package": "com.uberstup.clientpanel",
|
||||
"versionCode": 1,
|
||||
"permissions": [
|
||||
"android.permission.RECEIVE_BOOT_COMPLETED",
|
||||
"android.permission.VIBRATE",
|
||||
"android.permission.POST_NOTIFICATIONS"
|
||||
"android.permission.VIBRATE"
|
||||
]
|
||||
},
|
||||
"web": {
|
||||
@@ -37,16 +34,7 @@
|
||||
},
|
||||
"plugins": [
|
||||
"expo-font",
|
||||
"expo-router",
|
||||
[
|
||||
"expo-notifications",
|
||||
{
|
||||
"icon": "./assets/icon.png",
|
||||
"color": "#000000",
|
||||
"defaultChannel": "orders",
|
||||
"sounds": []
|
||||
}
|
||||
]
|
||||
"expo-router"
|
||||
],
|
||||
"extra": {
|
||||
"eas": {
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "358179533147",
|
||||
"project_id": "milieu-nantais",
|
||||
"storage_bucket": "milieu-nantais.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:358179533147:android:20728599f7c862e2362a7e",
|
||||
"android_client_info": {
|
||||
"package_name": "com.uberstup.clientpanel"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyDrmjxyOKq5r2VXwfrfiNx-aQrGwNkiyCs"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "milieu-nantais",
|
||||
"private_key_id": "51a81342e30ff18ab089745e431631c6e060687f",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDPOZDgOSeDfSpu\n/pCQube8ltjsBCFlIjxuUDpN5d+3GPpH7GZlUnNzScajw9WwLrprvzcAjnaN7Nx5\nyrv/SijLPzVIL9ntniDb/reCh+XycMz36zmuxLzFZcyQmYaYOl3XcVjm5u8GC+l6\nIFeHK55WymhH3/dygssGeLGLJRrTbfkj6YL+CqHM+roomdBlAQNh8Ds+XauyeCZ6\n/fDFlKoyloLKtbvjdxRN0dTP44r93afFFDJjGsLxDyMSmB/JO2cxD+hsndTOnOYI\n8BNyFxukYgTICmSn32CELY1ZTnNpDB7u7VZLwZFgOXxJRp5GblddXfW2KOZa0zxW\nf7c+r8mFAgMBAAECggEABgvjtIsuYjmNeqoeXT6yBARxxWc0cz89vKlGHj5CGhVW\nPjGBZlzyxe8l5uLgshCW+2xbpj5B6s+5uisGc8M97/pC632L8hE+W7TxtVMSTFHq\nxSQdrYILkQXwo1p7cSco1E+4HJqp6EcCM8BMVYMaDmW/B7PbBEIZOvHcGleFkhwy\npGMziXZ9xr8WozrbMCPNpLTLbAZvOxWTC0ZEpj5Bwz2LIwLqiHDSfk74xsLnn+Gp\n7Ed91so7vz9fRJxfT7QlVnHzFZ8YGCQrUl1aq5yy8KuPMn5J1XuQOdy1XbMy1B44\nHQR1+4FnYzrcBgaFgFwGyZk1Mi/sDMgKGcp4HZZKWQKBgQD1NM93aXbeEaAD6gzH\nz8HCCy1GRJTS9/r0mpx4i3kqUlSZkh0zO7UGUXENZ4WOF5OTH4YtqNuOpzUxZhy2\nTkXLlyT6GgWlQYcWkum6FGmFpRPeWxImT2ClhL68+UoqzkwDho3ZWMgaZZfc//rI\ngoVDYRyMsh3A/qBngUKPzYKHqQKBgQDYWL/B7YhR37gatRjPBJBx955+Hrgts8m4\nTlDySOm7LrMvQ12XCEzj9ylgmE2ah+/PG2AEWutLQBiYSCiN+/PUFXHWlYHmfdJg\nX/IQAfVm9UxcTYLklJ460tyX5IJ0rGpUeSF4mkxhX71mig02RUuwmdNTnCzZsbtd\naCfqBR6sfQKBgGr6D0lZibJ4ngcXJVxHF5FArw/o+8TOy33LtlghAUulf6NAS9z+\nP4vyHBBtCadkQc3+AtwIv0ENUferRPuESo738fnrQGtIm1cN4Up9fUwBKRnpQZHV\nL6UGtPBdEi56sk8XdOtOAH8Ds62HQDIaP2mWBI6dZr28WN7PVerHDhuBAoGAIjP9\ntPIRoCgHWimRT4FAONRV+UhwA8GtvXacM4G1egNLDsjOrgcA6PV1VDEf67NcBVkl\nl+qr0kzC1qhpyPCxPrFZOvyN+duge94PTdqRaoKTXPBgJjUcwt39RAI7Yai0csBn\nu0Jhmu9g6SogJplT+wqGr2w8ZvpDTeQek0/V5p0CgYBC4inrAe28IPPqY7NtBHX/\nE939v79U+pA3Xq1WyLfgLd6QrUHsRMO5xvObSUlNrA2U+QQiLkygDnxE0VK9qdYK\nN/zsbZZQdTuDSy1NcMJ50h0ed4CrmYn+mJS3rGlj0Ov9vPa4Ppf/E8Of1uwFFiB4\ng3QpbaO3cuvxiu4EHf25Lg==\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "firebase-adminsdk-fbsvc@milieu-nantais.iam.gserviceaccount.com",
|
||||
"client_id": "102638886608570330042",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40milieu-nantais.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
||||
@@ -10,15 +10,6 @@ import React, {
|
||||
import { getClientNotifications, markNotificationsRead } from "../api/api";
|
||||
import type { ClientNotification } from "../api/api";
|
||||
import { getToken } from "../auth/tokenStorage";
|
||||
import {
|
||||
registerForPushNotificationsAsync,
|
||||
sendPushTokenToBackend,
|
||||
setupNotificationChannel,
|
||||
addNotificationReceivedListener,
|
||||
addNotificationResponseListener,
|
||||
setBadgeCount,
|
||||
getLastNotificationResponse,
|
||||
} from "../services/pushNotifications";
|
||||
|
||||
interface ToastData {
|
||||
message: string;
|
||||
@@ -32,7 +23,6 @@ interface NotificationContextType {
|
||||
refreshNotifications: () => Promise<void>;
|
||||
toast: ToastData | null;
|
||||
clearToast: () => void;
|
||||
pushToken: string | null;
|
||||
navigateToOrder: number | null;
|
||||
clearNavigateToOrder: () => void;
|
||||
}
|
||||
@@ -44,7 +34,6 @@ const NotificationContext = createContext<NotificationContextType>({
|
||||
refreshNotifications: async () => {},
|
||||
toast: null,
|
||||
clearToast: () => {},
|
||||
pushToken: null,
|
||||
navigateToOrder: null,
|
||||
clearNavigateToOrder: () => {},
|
||||
});
|
||||
@@ -57,35 +46,30 @@ function getToastType(
|
||||
notifType: string,
|
||||
): "success" | "error" | "warning" | "info" {
|
||||
switch (notifType) {
|
||||
case "order_confirmed":
|
||||
case "order_approved":
|
||||
case "assigned":
|
||||
return "success";
|
||||
case "order_en_route":
|
||||
case "order_assigned":
|
||||
case "en_route":
|
||||
case "support":
|
||||
return "info";
|
||||
case "order_delivered":
|
||||
return "warning";
|
||||
case "livre":
|
||||
return "success";
|
||||
case "failed":
|
||||
return "error";
|
||||
default:
|
||||
return "info";
|
||||
}
|
||||
}
|
||||
|
||||
export function NotificationProvider({ children }: { children: ReactNode }) {
|
||||
const [notifications, setNotifications] = useState<ClientNotification[]>(
|
||||
[],
|
||||
);
|
||||
const [notifications, setNotifications] = useState<ClientNotification[]>([]);
|
||||
const [unreadCount, setUnreadCount] = useState(0);
|
||||
const [toast, setToast] = useState<ToastData | null>(null);
|
||||
const [pushToken, setPushToken] = useState<string | null>(null);
|
||||
const [navigateToOrder, setNavigateToOrder] = useState<number | null>(null);
|
||||
const seenIdsRef = useRef<Set<string>>(new Set());
|
||||
const isFirstLoadRef = useRef(true);
|
||||
|
||||
const clearToast = useCallback(() => setToast(null), []);
|
||||
const clearNavigateToOrder = useCallback(
|
||||
() => setNavigateToOrder(null),
|
||||
[],
|
||||
);
|
||||
const clearNavigateToOrder = useCallback(() => setNavigateToOrder(null), []);
|
||||
|
||||
const showToast = useCallback(
|
||||
(message: string, type: ToastData["type"]) => {
|
||||
@@ -95,68 +79,6 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
const initPush = async () => {
|
||||
const token = await getToken();
|
||||
if (!token) return;
|
||||
|
||||
await setupNotificationChannel();
|
||||
|
||||
const expoPushToken = await registerForPushNotificationsAsync();
|
||||
if (expoPushToken && isMounted) {
|
||||
setPushToken(expoPushToken);
|
||||
await sendPushTokenToBackend(expoPushToken);
|
||||
}
|
||||
|
||||
const lastResponse = await getLastNotificationResponse();
|
||||
if (lastResponse && isMounted) {
|
||||
const data = lastResponse.notification.request.content.data;
|
||||
if (data?.command_id) {
|
||||
setNavigateToOrder(data.command_id as number);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
initPush();
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const receivedSub = addNotificationReceivedListener((notification) => {
|
||||
const { title, body } = notification.request.content;
|
||||
const data = notification.request.content.data;
|
||||
|
||||
// Afficher un toast pour la notification push en foreground
|
||||
if (body) {
|
||||
const notifType = (data?.type as string) || "";
|
||||
showToast(body, getToastType(notifType));
|
||||
}
|
||||
|
||||
// Rafraîchir la liste des notifications
|
||||
fetchNotifications();
|
||||
});
|
||||
|
||||
// L'utilisateur tap sur une notification
|
||||
const responseSub = addNotificationResponseListener((response) => {
|
||||
const data = response.notification.request.content.data;
|
||||
if (data?.command_id) {
|
||||
setNavigateToOrder(data.command_id as number);
|
||||
}
|
||||
// Rafraîchir les notifications
|
||||
fetchNotifications();
|
||||
});
|
||||
|
||||
return () => {
|
||||
receivedSub.remove();
|
||||
responseSub.remove();
|
||||
};
|
||||
}, [showToast]);
|
||||
|
||||
const fetchNotifications = useCallback(async () => {
|
||||
const token = await getToken();
|
||||
if (!token) return;
|
||||
@@ -168,11 +90,7 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
|
||||
setNotifications(newNotifs);
|
||||
setUnreadCount(response.unread_count);
|
||||
|
||||
// Mettre à jour le badge de l'app
|
||||
await setBadgeCount(response.unread_count);
|
||||
|
||||
if (!isFirstLoadRef.current) {
|
||||
// Détecter les NOUVELLES notifications non lues
|
||||
for (const notif of newNotifs) {
|
||||
if (notif.read) continue;
|
||||
const key = `${notif.command_id}-${notif.type}-${notif.created_at}`;
|
||||
@@ -182,7 +100,6 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Premier chargement : enregistrer tous les IDs sans toast
|
||||
for (const notif of newNotifs) {
|
||||
const key = `${notif.command_id}-${notif.type}-${notif.created_at}`;
|
||||
seenIdsRef.current.add(key);
|
||||
@@ -191,7 +108,7 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Silencieux en cas d'erreur
|
||||
// Silencieux
|
||||
}
|
||||
}, [showToast]);
|
||||
|
||||
@@ -206,8 +123,6 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
|
||||
setNotifications((prev) =>
|
||||
prev.map((n) => ({ ...n, read: true })),
|
||||
);
|
||||
// Remettre le badge à 0
|
||||
await setBadgeCount(0);
|
||||
}
|
||||
} catch {
|
||||
// Silencieux
|
||||
@@ -230,7 +145,6 @@ export function NotificationProvider({ children }: { children: ReactNode }) {
|
||||
refreshNotifications: fetchNotifications,
|
||||
toast,
|
||||
clearToast,
|
||||
pushToken,
|
||||
navigateToOrder,
|
||||
clearNavigateToOrder,
|
||||
}}
|
||||
|
||||
@@ -18,7 +18,6 @@ import { useAuth } from "../auth/AuthContext";
|
||||
import { useTheme } from "../context/ThemeContext";
|
||||
import { logoutUser } from "../api/api";
|
||||
import type { ClientNotification } from "../api/api";
|
||||
import { removePushTokenFromBackend } from "../services/pushNotifications";
|
||||
import { fontSize, spacing, borderRadius } from "../theme";
|
||||
import Toast from "../components/ui/Toast";
|
||||
import type { ClientTabParamList, ClientStackParamList } from "./types";
|
||||
@@ -58,7 +57,6 @@ function ClientTabs() {
|
||||
markAllRead,
|
||||
toast,
|
||||
clearToast,
|
||||
pushToken,
|
||||
navigateToOrder,
|
||||
clearNavigateToOrder,
|
||||
} = useNotifications();
|
||||
@@ -76,7 +74,6 @@ function ClientTabs() {
|
||||
}, [navigateToOrder, navigation, clearNavigateToOrder]);
|
||||
|
||||
const handleLogout = async () => {
|
||||
try { await removePushTokenFromBackend(); } catch { /* silencieux */ }
|
||||
await logoutUser();
|
||||
await logout();
|
||||
};
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
import * as Notifications from "expo-notifications";
|
||||
import * as Device from "expo-device";
|
||||
import Constants from "expo-constants";
|
||||
import { Platform } from "react-native";
|
||||
import apiClient from "../api/client";
|
||||
|
||||
const V1 = "https://uber-stup.club/api/v1";
|
||||
|
||||
// Configuration du comportement des notifications en foreground
|
||||
Notifications.setNotificationHandler({
|
||||
handleNotification: async () => ({
|
||||
shouldPlaySound: true,
|
||||
shouldSetBadge: true,
|
||||
shouldShowBanner: true,
|
||||
shouldShowList: true,
|
||||
}),
|
||||
});
|
||||
|
||||
export async function setupNotificationChannel(): Promise<void> {
|
||||
if (Platform.OS === "android") {
|
||||
await Notifications.setNotificationChannelAsync("orders", {
|
||||
name: "Commandes",
|
||||
importance: Notifications.AndroidImportance.MAX,
|
||||
vibrationPattern: [0, 250, 250, 250],
|
||||
lightColor: "#7C3AED",
|
||||
sound: "default",
|
||||
enableVibrate: true,
|
||||
showBadge: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Enregistrer le device pour les push notifications et retourner le token
|
||||
export async function registerForPushNotificationsAsync(): Promise<
|
||||
string | null
|
||||
> {
|
||||
if (!Device.isDevice) {
|
||||
console.log(
|
||||
"⚠️ Push notifications ne fonctionnent pas sur un émulateur",
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Vérifier/demander les permissions
|
||||
const { status: existingStatus } =
|
||||
await Notifications.getPermissionsAsync();
|
||||
let finalStatus = existingStatus;
|
||||
|
||||
if (existingStatus !== "granted") {
|
||||
const { status } = await Notifications.requestPermissionsAsync();
|
||||
finalStatus = status;
|
||||
}
|
||||
|
||||
if (finalStatus !== "granted") {
|
||||
console.log("❌ Permission push notifications refusée");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Récupérer le projectId Expo
|
||||
const projectId =
|
||||
Constants.expoConfig?.extra?.eas?.projectId ??
|
||||
Constants.easConfig?.projectId;
|
||||
|
||||
if (!projectId) {
|
||||
console.log("⚠️ projectId non trouvé - push notifications désactivées");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const tokenData = await Notifications.getExpoPushTokenAsync({
|
||||
projectId,
|
||||
});
|
||||
const pushToken = tokenData.data;
|
||||
console.log("📱 Push token:", pushToken);
|
||||
return pushToken;
|
||||
} catch (error) {
|
||||
console.error("❌ Erreur récupération push token:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Envoyer le push token au backend
|
||||
export async function sendPushTokenToBackend(
|
||||
pushToken: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const { data } = await apiClient.post(`${V1}/push-token`, {
|
||||
push_token: pushToken,
|
||||
platform: Platform.OS,
|
||||
});
|
||||
console.log("✅ Push token enregistré sur le backend");
|
||||
return data.success === true;
|
||||
} catch (error) {
|
||||
console.error("❌ Erreur envoi push token au backend:", error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Supprimer le push token du backend (au logout)
|
||||
export async function removePushTokenFromBackend(): Promise<void> {
|
||||
try {
|
||||
await apiClient.delete(`${V1}/push-token`);
|
||||
console.log("✅ Push token supprimé du backend");
|
||||
} catch (error) {
|
||||
console.error("❌ Erreur suppression push token:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Listeners pour les notifications
|
||||
export function addNotificationReceivedListener(
|
||||
callback: (notification: Notifications.Notification) => void,
|
||||
): Notifications.EventSubscription {
|
||||
return Notifications.addNotificationReceivedListener(callback);
|
||||
}
|
||||
|
||||
export function addNotificationResponseListener(
|
||||
callback: (response: Notifications.NotificationResponse) => void,
|
||||
): Notifications.EventSubscription {
|
||||
return Notifications.addNotificationResponseReceivedListener(callback);
|
||||
}
|
||||
|
||||
// Mettre à jour le badge
|
||||
export async function setBadgeCount(count: number): Promise<void> {
|
||||
try {
|
||||
await Notifications.setBadgeCountAsync(count);
|
||||
} catch {
|
||||
// Silencieux - certains appareils ne supportent pas les badges
|
||||
}
|
||||
}
|
||||
|
||||
// Récupérer la dernière notification qui a ouvert l'app
|
||||
export async function getLastNotificationResponse(): Promise<Notifications.NotificationResponse | null> {
|
||||
return await Notifications.getLastNotificationResponseAsync();
|
||||
}
|
||||
Reference in New Issue
Block a user