diff --git a/src/containers/ChatInput/index.js b/src/containers/ChatInput/index.js index 0406c31..ba1271a 100644 --- a/src/containers/ChatInput/index.js +++ b/src/containers/ChatInput/index.js @@ -1,5 +1,5 @@ import React, { useState, useCallback, useEffect, useRef } from "react"; -import { View, Text, TouchableOpacity } from "react-native"; +import { View, Text, TouchableOpacity, Platform, Alert } from "react-native"; import { MaterialCommunityIcons } from "@expo/vector-icons"; import { useAudioRecorder, @@ -11,6 +11,14 @@ import { AudioQuality, } from "expo-audio"; +import { + check, + request, + PERMISSIONS, + RESULTS, + openSettings, +} from "react-native-permissions"; + import Countdown from "react-countdown"; import { useAlertState, getLocationState, useSessionState } from "~/stores"; @@ -74,6 +82,51 @@ const recordingOptionsFallback = { const activeOpacity = 0.7; +const withTimeout = (promise, ms = 10000) => + new Promise((resolve, reject) => { + const id = setTimeout( + () => reject(new Error("Permission request timeout")), + ms, + ); + promise + .then((v) => { + clearTimeout(id); + resolve(v); + }) + .catch((e) => { + clearTimeout(id); + reject(e); + }); + }); + +const ensureMicPermission = async () => { + if (Platform.OS !== "android") { + return true; + } + try { + const status = await check(PERMISSIONS.ANDROID.RECORD_AUDIO); + if (status === RESULTS.GRANTED) return true; + if (status === RESULTS.BLOCKED) { + try { + Alert.alert( + "Autorisation micro bloquée", + "Veuillez autoriser le micro dans les paramètres de l'application.", + [ + { text: "Annuler", style: "cancel" }, + { text: "Ouvrir les paramètres", onPress: openSettings }, + ], + ); + } catch (_) {} + return false; + } + const res = await request(PERMISSIONS.ANDROID.RECORD_AUDIO); + return res === RESULTS.GRANTED; + } catch (e) { + console.log("Mic permission check failed", e); + return false; + } +}; + export default React.memo(function ChatInput({ style, labelStyle, @@ -146,8 +199,30 @@ export default React.memo(function ChatInput({ const startRecording = useCallback(async () => { try { - console.log("Requesting permissions.."); - await requestRecordingPermissionsAsync(); + console.log("Requesting microphone permission.."); + const grantedPre = await ensureMicPermission(); + if (!grantedPre) { + console.log("Microphone permission not granted or blocked"); + return; + } + try { + await withTimeout(requestRecordingPermissionsAsync(), 10000); + } catch (permErr) { + console.log("Microphone permission request failed/timed out:", permErr); + if (Platform.OS === "android") { + try { + Alert.alert( + "Autorisation micro requise", + "Impossible d'obtenir l'autorisation du microphone. Ouvrir les paramètres pour l'accorder.", + [ + { text: "Annuler", style: "cancel" }, + { text: "Ouvrir les paramètres", onPress: openSettings }, + ], + ); + } catch (_) {} + } + return; + } await setAudioModeAsync({ allowsRecording: true, interruptionMode: "doNotMix", diff --git a/src/permissions/requestPermissionFcm.js b/src/permissions/requestPermissionFcm.js index 1e9b133..0d391a8 100644 --- a/src/permissions/requestPermissionFcm.js +++ b/src/permissions/requestPermissionFcm.js @@ -46,22 +46,27 @@ export default async () => { // Handle Android permissions permissionLogger.debug("Requesting Android notification permissions"); - const { status } = await requestNotifications(["alert"]); + const { status } = await requestNotifications(["alert", "sound", "badge"]); - permissionLogger.debug( - "Requesting POST_NOTIFICATIONS permission (Android 13+)", - ); - const postNotifications = await request( - PERMISSIONS.ANDROID.POST_NOTIFICATIONS, - ); + let postNotifications = RESULTS.UNAVAILABLE; + if (Platform.Version >= 33) { + permissionLogger.debug( + "Requesting POST_NOTIFICATIONS permission (Android 13+)", + ); + postNotifications = await request(PERMISSIONS.ANDROID.POST_NOTIFICATIONS); + } + // Determine grant state: + // - Android 13+ requires POST_NOTIFICATIONS granted + // - Below 13, POST_NOTIFICATIONS is unavailable; use requestNotifications result const isGranted = - status === RESULTS.GRANTED && - (postNotifications === "granted" || postNotifications === "unavailable"); + (Platform.Version >= 33 && postNotifications === RESULTS.GRANTED) || + (Platform.Version < 33 && status === RESULTS.GRANTED); permissionLogger.info("Android notification permission result", { notificationStatus: status, postNotificationsStatus: postNotifications, + osVersion: Platform.Version, granted: isGranted, }); diff --git a/src/permissions/usePermissionFcm.js b/src/permissions/usePermissionFcm.js index fc097e9..352d7a5 100644 --- a/src/permissions/usePermissionFcm.js +++ b/src/permissions/usePermissionFcm.js @@ -3,23 +3,17 @@ import { useEffect } from "react"; import requestPermissionFcm from "./requestPermissionFcm"; -export default async function usePermissionFcm() { +export default function usePermissionFcm() { const { fcm } = usePermissionsState(["fcm"]); const { setFcm } = permissionsActions; useEffect(() => { - // if (fcm) { - // return; - // } + if (fcm) { + return; + } (async () => { - // const authStatus = await messaging().requestPermission(); - // setFcm( - // authStatus === AuthorizationStatus.AUTHORIZED || - // authStatus === AuthorizationStatus.PROVISIONAL, - // ); const granted = await requestPermissionFcm(); - setFcm(granted); })(); }, [fcm, setFcm]);