From b1a41c4a16e9703dbcd9dafb125abbd3b58b50ce Mon Sep 17 00:00:00 2001 From: Xor290 Date: Sat, 14 Mar 2026 18:28:02 +0100 Subject: [PATCH] chore: add push notif --- mobile/app.json | 14 +++-- mobile/google-services.json | 29 ++++++++++ mobile/src/api/api.ts | 16 ++++++ mobile/src/context/NotificationContext.tsx | 26 +++++++++ mobile/src/services/pushNotifications.ts | 63 ++++++++++++++++++++++ 5 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 mobile/google-services.json create mode 100644 mobile/src/services/pushNotifications.ts diff --git a/mobile/app.json b/mobile/app.json index 1db1cbc7..9f13ace0 100644 --- a/mobile/app.json +++ b/mobile/app.json @@ -25,9 +25,8 @@ "predictiveBackGestureEnabled": false, "package": "com.uberstup.clientpanel", "versionCode": 1, - "permissions": [ - "android.permission.VIBRATE" - ] + "permissions": ["android.permission.VIBRATE"], + "googleServicesFile": "./google-services.json" }, "web": { "favicon": "./assets/icon.png" @@ -35,6 +34,15 @@ "plugins": [ "expo-font", "expo-router", + [ + "expo-notifications", + { + "icon": "./assets/icon.png", + "color": "#ffffff", + "androidMode": "default", + "androidCollapsedTitle": "#{unread_notifications} nouvelles notifications" + } + ], [ "expo-build-properties", { diff --git a/mobile/google-services.json b/mobile/google-services.json new file mode 100644 index 00000000..2f49d8bd --- /dev/null +++ b/mobile/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "607244438046", + "project_id": "frontend-client-f3354", + "storage_bucket": "frontend-client-f3354.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:607244438046:android:ce55d750fd0f1b98ac3c0f", + "android_client_info": { + "package_name": "com.uberstup.clientpanel" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyA2-og1AoVmwXXY4KTX-J-bt6E5cjiQvmM" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/mobile/src/api/api.ts b/mobile/src/api/api.ts index ca718f39..daad3e24 100644 --- a/mobile/src/api/api.ts +++ b/mobile/src/api/api.ts @@ -777,6 +777,22 @@ export const getPublicSettings = async (): Promise => { } }; +export const registerPushToken = async (token: string): Promise => { + try { + await apiClient.post(`${V1}/push-token`, { token }); + } catch { + // silencieux + } +}; + +export const unregisterPushToken = async (): Promise => { + try { + await apiClient.delete(`${V1}/push-token`); + } catch { + // silencieux + } +}; + export const calculateOrderTotal = (order: any): number => { if (typeof order.total === "number" && order.total > 0) return order.total; if (typeof order.total_prix === "number" && order.total_prix > 0) diff --git a/mobile/src/context/NotificationContext.tsx b/mobile/src/context/NotificationContext.tsx index a9187ccc..a3ca494d 100644 --- a/mobile/src/context/NotificationContext.tsx +++ b/mobile/src/context/NotificationContext.tsx @@ -7,9 +7,11 @@ import React, { useCallback, type ReactNode, } from "react"; +import * as Notifications from "expo-notifications"; import { getClientNotifications, markNotificationsRead } from "../api/api"; import type { ClientNotification } from "../api/api"; import { getToken } from "../auth/tokenStorage"; +import { registerForPushNotifications } from "../services/pushNotifications"; interface ToastData { message: string; @@ -128,6 +130,30 @@ export function NotificationProvider({ children }: { children: ReactNode }) { } }, []); + // Enregistrement push token + listeners (pattern doc Expo) + useEffect(() => { + registerForPushNotifications().catch(console.error); + + const notificationListener = Notifications.addNotificationReceivedListener((notif) => { + const body = notif.request.content.body ?? ""; + const data = notif.request.content.data as any; + showToast(body, getToastType(data?.type ?? "")); + fetchNotifications(); + }); + + const responseListener = Notifications.addNotificationResponseReceivedListener((response) => { + const data = response.notification.request.content.data as any; + if (data?.command_id) { + setNavigateToOrder(data.command_id); + } + }); + + return () => { + notificationListener.remove(); + responseListener.remove(); + }; + }, [fetchNotifications, showToast]); + // Polling toutes les 15 secondes useEffect(() => { fetchNotifications(); diff --git a/mobile/src/services/pushNotifications.ts b/mobile/src/services/pushNotifications.ts new file mode 100644 index 00000000..0c0bdcfb --- /dev/null +++ b/mobile/src/services/pushNotifications.ts @@ -0,0 +1,63 @@ +import * as Notifications from "expo-notifications"; +import * as Device from "expo-device"; +import Constants from "expo-constants"; +import { Platform, Alert } from "react-native"; +import { registerPushToken } from "../api/api"; + +// Comportement des notifs reçues en foreground +Notifications.setNotificationHandler({ + handleNotification: async () => ({ + shouldPlaySound: true, + shouldSetBadge: true, + shouldShowBanner: true, + shouldShowList: true, + }), +}); + +function handleRegistrationError(errorMessage: string) { + console.error("[PUSH]", errorMessage); + Alert.alert("Push Notification Error", errorMessage); +} + +export async function registerForPushNotifications(): Promise { + if (Platform.OS === "android") { + await Notifications.setNotificationChannelAsync("default", { + name: "default", + importance: Notifications.AndroidImportance.MAX, + vibrationPattern: [0, 250, 250, 250], + lightColor: "#FF231F7C", + }); + } + + if (Device.isDevice) { + const { status: existingStatus } = await Notifications.getPermissionsAsync(); + let finalStatus = existingStatus; + if (existingStatus !== "granted") { + const { status } = await Notifications.requestPermissionsAsync(); + finalStatus = status; + } + if (finalStatus !== "granted") { + handleRegistrationError("Permission not granted to get push token for push notification!"); + return; + } + const projectId = + Constants?.expoConfig?.extra?.eas?.projectId ?? + Constants?.easConfig?.projectId; + if (!projectId) { + handleRegistrationError("Project ID not found"); + return; + } + try { + const pushTokenString = ( + await Notifications.getExpoPushTokenAsync({ projectId }) + ).data; + console.log("[PUSH] Token obtenu:", pushTokenString); + await registerPushToken(pushTokenString); + return pushTokenString; + } catch (e: unknown) { + handleRegistrationError(`${e}`); + } + } else { + handleRegistrationError("Must use physical device for push notifications"); + } +}