Compare commits
No commits in common. "cccb49134f55faf5f5d536e91509eba2743f2af8" and "492904cbe8a5ceec35a79610ea59cf5eaed42735" have entirely different histories.
cccb49134f
...
492904cbe8
7 changed files with 88 additions and 143 deletions
|
|
@ -1,75 +1,20 @@
|
||||||
import { Platform, Linking, PermissionsAndroid } from "react-native";
|
import { Platform, Linking } from "react-native";
|
||||||
import RNImmediatePhoneCall from "react-native-immediate-phone-call";
|
import RNImmediatePhoneCall from "react-native-immediate-phone-call";
|
||||||
|
|
||||||
export async function phoneCallEmergency() {
|
export function phoneCallEmergency() {
|
||||||
const emergencyNumber = "112";
|
const emergencyNumber = "112";
|
||||||
|
|
||||||
if (Platform.OS === "ios") {
|
if (Platform.OS === "ios") {
|
||||||
try {
|
// Use telprompt URL scheme on iOS
|
||||||
// Prefer telprompt on iOS for immediate prompt, fallback to tel
|
Linking.openURL(`telprompt:${emergencyNumber}`).catch((err) => {
|
||||||
await Linking.openURL(`telprompt:${emergencyNumber}`);
|
console.error("Error opening phone dialer:", err);
|
||||||
} catch (err) {
|
// Fallback to regular tel: if telprompt fails
|
||||||
console.error("Error opening phone dialer (iOS telprompt):", err);
|
Linking.openURL(`tel:${emergencyNumber}`).catch((err) => {
|
||||||
try {
|
console.error("Error opening phone dialer (fallback):", err);
|
||||||
await Linking.openURL(`tel:${emergencyNumber}`);
|
});
|
||||||
} catch (err2) {
|
});
|
||||||
console.error("Error opening phone dialer (iOS tel fallback):", err2);
|
} else {
|
||||||
}
|
// Use RNImmediatePhoneCall on Android
|
||||||
}
|
RNImmediatePhoneCall.immediatePhoneCall(emergencyNumber);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Android: request CALL_PHONE upfront and provide deterministic fallback
|
|
||||||
try {
|
|
||||||
const granted = await PermissionsAndroid.request(
|
|
||||||
PermissionsAndroid.PERMISSIONS.CALL_PHONE,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
|
|
||||||
// Try immediate call, but arm a short fallback timer in case the OS/OEM ignores ACTION_CALL
|
|
||||||
let fallbackTriggered = false;
|
|
||||||
|
|
||||||
const triggerFallback = async () => {
|
|
||||||
if (fallbackTriggered) return;
|
|
||||||
fallbackTriggered = true;
|
|
||||||
try {
|
|
||||||
await Linking.openURL(`tel:${emergencyNumber}`);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Fallback to dialer failed:", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fire fallback after ~1.2s if nothing happens
|
|
||||||
const timer = setTimeout(triggerFallback, 1200);
|
|
||||||
|
|
||||||
try {
|
|
||||||
RNImmediatePhoneCall.immediatePhoneCall(emergencyNumber);
|
|
||||||
} catch (callErr) {
|
|
||||||
// If native throws synchronously, cancel timer and fallback immediately
|
|
||||||
clearTimeout(timer);
|
|
||||||
await triggerFallback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give a little extra time; if no fallback was needed, clear the timer
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!fallbackTriggered) clearTimeout(timer);
|
|
||||||
}, 3000);
|
|
||||||
} else {
|
|
||||||
// Permission denied or never-ask-again: open dialer prompt
|
|
||||||
try {
|
|
||||||
await Linking.openURL(`tel:${emergencyNumber}`);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Permission denied; dialer fallback failed:", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("CALL_PHONE permission request failed:", err);
|
|
||||||
// Last resort: open dialer
|
|
||||||
try {
|
|
||||||
await Linking.openURL(`tel:${emergencyNumber}`);
|
|
||||||
} catch (err2) {
|
|
||||||
console.error("Dialer fallback failed after permission error:", err2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ export default function AccountManagement({
|
||||||
profileData,
|
profileData,
|
||||||
openAccountModal,
|
openAccountModal,
|
||||||
waitingSmsType,
|
waitingSmsType,
|
||||||
clearAuthWaitParams,
|
|
||||||
}) {
|
}) {
|
||||||
const { colors, custom } = useTheme();
|
const { colors, custom } = useTheme();
|
||||||
const isConnected = isConnectedProfile(profileData);
|
const isConnected = isConnectedProfile(profileData);
|
||||||
|
|
@ -137,7 +136,6 @@ export default function AccountManagement({
|
||||||
modalState={modalState}
|
modalState={modalState}
|
||||||
profileData={profileData}
|
profileData={profileData}
|
||||||
waitingSmsType={waitingSmsType}
|
waitingSmsType={waitingSmsType}
|
||||||
clearAuthWaitParams={clearAuthWaitParams}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ export default function AccountManagementModal({
|
||||||
modalState,
|
modalState,
|
||||||
profileData,
|
profileData,
|
||||||
waitingSmsType,
|
waitingSmsType,
|
||||||
clearAuthWaitParams,
|
|
||||||
}) {
|
}) {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const [modal, setModal] = modalState;
|
const [modal, setModal] = modalState;
|
||||||
|
|
@ -39,7 +38,6 @@ export default function AccountManagementModal({
|
||||||
authMethod={authMethod}
|
authMethod={authMethod}
|
||||||
setAuthMethod={setAuthMethod}
|
setAuthMethod={setAuthMethod}
|
||||||
waitingSmsType={waitingSmsType}
|
waitingSmsType={waitingSmsType}
|
||||||
clearAuthWaitParams={clearAuthWaitParams}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{visible && component === "destroy" && (
|
{visible && component === "destroy" && (
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useCallback, useState, useEffect } from "react";
|
import React, { useCallback, useState, useEffect } from "react";
|
||||||
|
|
||||||
import { View } from "react-native";
|
import { View, Alert } from "react-native";
|
||||||
import LittleLoader from "~/components/LittleLoader";
|
import LittleLoader from "~/components/LittleLoader";
|
||||||
import { Button } from "react-native-paper";
|
import { Button } from "react-native-paper";
|
||||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||||
|
|
@ -29,7 +29,6 @@ export default function AccountManagementModalConnect({
|
||||||
authMethod,
|
authMethod,
|
||||||
setAuthMethod,
|
setAuthMethod,
|
||||||
waitingSmsType,
|
waitingSmsType,
|
||||||
clearAuthWaitParams,
|
|
||||||
}) {
|
}) {
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const { colors, custom } = useTheme();
|
const { colors, custom } = useTheme();
|
||||||
|
|
@ -71,12 +70,20 @@ export default function AccountManagementModalConnect({
|
||||||
};
|
};
|
||||||
}, [isLoading, loginRequest]);
|
}, [isLoading, loginRequest]);
|
||||||
|
|
||||||
const connectUsingPhoneNumber = () => {
|
const connectUsingPhoneNumber = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
sendAuthSMS({
|
try {
|
||||||
smsType: "C",
|
await sendAuthSMS({
|
||||||
body: "Se connecter sur Alerte-Secours:\nCode: [CODE]\n💙", // must don't exceed 160 chars including replaced [CODE]
|
smsType: "C",
|
||||||
});
|
body: "Se connecter sur Alerte-Secours:\nCode: [CODE]\n💙", // must don't exceed 160 chars including replaced [CODE]
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
setIsLoading(false);
|
||||||
|
Alert.alert(
|
||||||
|
"Échec de l’ouverture des SMS",
|
||||||
|
"Impossible d’ouvrir l’application SMS. Réessayez.",
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const smsDisclaimerModalStatePair = useState({ visible: false });
|
const smsDisclaimerModalStatePair = useState({ visible: false });
|
||||||
|
|
@ -93,39 +100,20 @@ export default function AccountManagementModalConnect({
|
||||||
const [loginConfirmRequest] = useMutation(LOGIN_CONFIRM_MUTATION);
|
const [loginConfirmRequest] = useMutation(LOGIN_CONFIRM_MUTATION);
|
||||||
|
|
||||||
const confirmLoginRequest = useCallback(async () => {
|
const confirmLoginRequest = useCallback(async () => {
|
||||||
try {
|
const deviceUuid = await getDeviceUuid();
|
||||||
const deviceUuid = await getDeviceUuid();
|
const {
|
||||||
const {
|
data: {
|
||||||
data: {
|
doAuthLoginConfimLoginRequest: { authTokenJwt },
|
||||||
doAuthLoginConfimLoginRequest: { authTokenJwt },
|
},
|
||||||
},
|
} = await loginConfirmRequest({
|
||||||
} = await loginConfirmRequest({
|
variables: { loginRequestId: loginRequest.id, deviceUuid },
|
||||||
variables: { loginRequestId: loginRequest.id, deviceUuid },
|
});
|
||||||
});
|
await authActions.confirmLoginRequest({ authTokenJwt, isConnected });
|
||||||
await authActions.confirmLoginRequest({ authTokenJwt, isConnected });
|
}, [loginConfirmRequest, loginRequest?.id, isConnected]);
|
||||||
setIsLoading(false);
|
|
||||||
clearAuthWaitParams?.();
|
|
||||||
closeModal();
|
|
||||||
} catch (e) {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
loginConfirmRequest,
|
|
||||||
loginRequest?.id,
|
|
||||||
isConnected,
|
|
||||||
clearAuthWaitParams,
|
|
||||||
closeModal,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const rejectLoginRequest = useCallback(async () => {
|
const rejectLoginRequest = useCallback(async () => {
|
||||||
try {
|
await deleteLoginRequest({ variables: { id: loginRequest.id } });
|
||||||
await deleteLoginRequest({ variables: { id: loginRequest.id } });
|
}, [deleteLoginRequest, loginRequest]);
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
clearAuthWaitParams?.();
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
}, [deleteLoginRequest, loginRequest?.id, clearAuthWaitParams, closeModal]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ export default function Form({
|
||||||
profileData,
|
profileData,
|
||||||
openAccountModal,
|
openAccountModal,
|
||||||
waitingSmsType,
|
waitingSmsType,
|
||||||
clearAuthWaitParams,
|
|
||||||
}) {
|
}) {
|
||||||
const { userId } = useSessionState(["userId"]);
|
const { userId } = useSessionState(["userId"]);
|
||||||
|
|
||||||
|
|
@ -154,11 +153,7 @@ export default function Form({
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PhoneNumbers
|
<PhoneNumbers data={profileData} waitingSmsType={waitingSmsType} />
|
||||||
data={profileData}
|
|
||||||
waitingSmsType={waitingSmsType}
|
|
||||||
clearAuthWaitParams={clearAuthWaitParams}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
|
@ -195,7 +190,6 @@ export default function Form({
|
||||||
profileData={profileData}
|
profileData={profileData}
|
||||||
openAccountModal={openAccountModal}
|
openAccountModal={openAccountModal}
|
||||||
waitingSmsType={waitingSmsType}
|
waitingSmsType={waitingSmsType}
|
||||||
clearAuthWaitParams={clearAuthWaitParams}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
|
|
||||||
import { View } from "react-native";
|
import { View, Alert } from "react-native";
|
||||||
|
|
||||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||||
|
|
||||||
|
|
@ -26,11 +26,7 @@ import {
|
||||||
|
|
||||||
import useSendAuthSMS from "~/hooks/useSendAuthSMS";
|
import useSendAuthSMS from "~/hooks/useSendAuthSMS";
|
||||||
|
|
||||||
export default function PhoneNumbersView({
|
export default function PhoneNumbersView({ data, waitingSmsType }) {
|
||||||
data,
|
|
||||||
waitingSmsType,
|
|
||||||
clearAuthWaitParams,
|
|
||||||
}) {
|
|
||||||
const [isLoading, setIsLoading] = useState(waitingSmsType === "R" || false);
|
const [isLoading, setIsLoading] = useState(waitingSmsType === "R" || false);
|
||||||
const phoneNumberList = data.selectOneUser.manyPhoneNumber;
|
const phoneNumberList = data.selectOneUser.manyPhoneNumber;
|
||||||
|
|
||||||
|
|
@ -45,10 +41,18 @@ export default function PhoneNumbersView({
|
||||||
|
|
||||||
const registerPhoneNumber = useCallback(async () => {
|
const registerPhoneNumber = useCallback(async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
sendAuthSMS({
|
try {
|
||||||
smsType: "R",
|
await sendAuthSMS({
|
||||||
body: "S'enregistrer sur Alerte-Secours:\nCode: [CODE]\n💙", // must don't exceed 160 chars including replaced [CODE]
|
smsType: "R",
|
||||||
});
|
body: "S'enregistrer sur Alerte-Secours:\nCode: [CODE]\n💙", // must don't exceed 160 chars including replaced [CODE]
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
setIsLoading(false);
|
||||||
|
Alert.alert(
|
||||||
|
"Échec de l’ouverture des SMS",
|
||||||
|
"Impossible d’ouvrir l’application SMS. Réessayez.",
|
||||||
|
);
|
||||||
|
}
|
||||||
}, [sendAuthSMS, setIsLoading]);
|
}, [sendAuthSMS, setIsLoading]);
|
||||||
|
|
||||||
// Clear loading state after 3 minutes
|
// Clear loading state after 3 minutes
|
||||||
|
|
@ -73,16 +77,8 @@ export default function PhoneNumbersView({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data.selectOneUser.oneUserLoginRequest) {
|
if (data.selectOneUser.oneUserLoginRequest) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
clearAuthWaitParams?.();
|
|
||||||
}
|
}
|
||||||
}, [data.selectOneUser.oneUserLoginRequest, clearAuthWaitParams]);
|
}, [data.selectOneUser.oneUserLoginRequest]);
|
||||||
|
|
||||||
// Defensive cleanup on unmount to ensure no lingering loader
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
setIsLoading(false);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const deletePhoneNumberModalStatePair = useState({ visible: false });
|
const deletePhoneNumberModalStatePair = useState({ visible: false });
|
||||||
const [deletePhoneNumberModalState, setDeletePhoneNumberModalState] =
|
const [deletePhoneNumberModalState, setDeletePhoneNumberModalState] =
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
|
|
||||||
import { ScrollView, View } from "react-native";
|
import { ScrollView, View, AppState } from "react-native";
|
||||||
|
|
||||||
import Loader from "~/components/Loader";
|
import Loader from "~/components/Loader";
|
||||||
import { useSubscription } from "@apollo/client";
|
import { useSubscription } from "@apollo/client";
|
||||||
|
|
@ -12,6 +12,7 @@ import { createLogger } from "~/lib/logger";
|
||||||
import { FEATURE_SCOPES } from "~/lib/logger/scopes";
|
import { FEATURE_SCOPES } from "~/lib/logger/scopes";
|
||||||
|
|
||||||
import withConnectivity from "~/hoc/withConnectivity";
|
import withConnectivity from "~/hoc/withConnectivity";
|
||||||
|
import { useFocusEffect } from "@react-navigation/native";
|
||||||
|
|
||||||
import Form from "./Form";
|
import Form from "./Form";
|
||||||
|
|
||||||
|
|
@ -37,12 +38,38 @@ export default withConnectivity(function Profile({ navigation, route }) {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [userId]);
|
}, [userId]);
|
||||||
|
|
||||||
const clearAuthWaitParams = React.useCallback(() => {
|
useFocusEffect(
|
||||||
navigation.setParams({
|
React.useCallback(() => {
|
||||||
waitingSmsType: undefined,
|
restart();
|
||||||
openAccountModal: undefined,
|
}, [restart]),
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const sub = AppState.addEventListener("change", (state) => {
|
||||||
|
if (state === "active") {
|
||||||
|
restart();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}, [navigation]);
|
return () => {
|
||||||
|
sub?.remove?.();
|
||||||
|
};
|
||||||
|
}, [restart]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
route.params?.waitingSmsType &&
|
||||||
|
data?.selectOneUser?.oneUserLoginRequest
|
||||||
|
) {
|
||||||
|
navigation.setParams({
|
||||||
|
waitingSmsType: undefined,
|
||||||
|
openAccountModal: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
route.params?.waitingSmsType,
|
||||||
|
data?.selectOneUser?.oneUserLoginRequest,
|
||||||
|
navigation,
|
||||||
|
]);
|
||||||
|
|
||||||
if (loading || !data?.selectOneUser) {
|
if (loading || !data?.selectOneUser) {
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
|
|
@ -60,7 +87,6 @@ export default withConnectivity(function Profile({ navigation, route }) {
|
||||||
profileData={data}
|
profileData={data}
|
||||||
openAccountModal={route.params?.openAccountModal}
|
openAccountModal={route.params?.openAccountModal}
|
||||||
waitingSmsType={route.params?.waitingSmsType}
|
waitingSmsType={route.params?.waitingSmsType}
|
||||||
clearAuthWaitParams={clearAuthWaitParams}
|
|
||||||
/>
|
/>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue