Compare commits
3 commits
492904cbe8
...
cccb49134f
| Author | SHA1 | Date | |
|---|---|---|---|
| cccb49134f | |||
| 52fc3bc24b | |||
| 54083205f0 |
7 changed files with 143 additions and 88 deletions
|
|
@ -1,20 +1,75 @@
|
||||||
import { Platform, Linking } from "react-native";
|
import { Platform, Linking, PermissionsAndroid } from "react-native";
|
||||||
import RNImmediatePhoneCall from "react-native-immediate-phone-call";
|
import RNImmediatePhoneCall from "react-native-immediate-phone-call";
|
||||||
|
|
||||||
export function phoneCallEmergency() {
|
export async function phoneCallEmergency() {
|
||||||
const emergencyNumber = "112";
|
const emergencyNumber = "112";
|
||||||
|
|
||||||
if (Platform.OS === "ios") {
|
if (Platform.OS === "ios") {
|
||||||
// Use telprompt URL scheme on iOS
|
try {
|
||||||
Linking.openURL(`telprompt:${emergencyNumber}`).catch((err) => {
|
// Prefer telprompt on iOS for immediate prompt, fallback to tel
|
||||||
console.error("Error opening phone dialer:", err);
|
await Linking.openURL(`telprompt:${emergencyNumber}`);
|
||||||
// Fallback to regular tel: if telprompt fails
|
} catch (err) {
|
||||||
Linking.openURL(`tel:${emergencyNumber}`).catch((err) => {
|
console.error("Error opening phone dialer (iOS telprompt):", err);
|
||||||
console.error("Error opening phone dialer (fallback):", err);
|
try {
|
||||||
});
|
await Linking.openURL(`tel:${emergencyNumber}`);
|
||||||
});
|
} catch (err2) {
|
||||||
} else {
|
console.error("Error opening phone dialer (iOS tel fallback):", err2);
|
||||||
// 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,6 +20,7 @@ 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);
|
||||||
|
|
@ -136,6 +137,7 @@ export default function AccountManagement({
|
||||||
modalState={modalState}
|
modalState={modalState}
|
||||||
profileData={profileData}
|
profileData={profileData}
|
||||||
waitingSmsType={waitingSmsType}
|
waitingSmsType={waitingSmsType}
|
||||||
|
clearAuthWaitParams={clearAuthWaitParams}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ 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;
|
||||||
|
|
@ -38,6 +39,7 @@ 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, Alert } from "react-native";
|
import { View } 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,6 +29,7 @@ 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();
|
||||||
|
|
@ -70,20 +71,12 @@ export default function AccountManagementModalConnect({
|
||||||
};
|
};
|
||||||
}, [isLoading, loginRequest]);
|
}, [isLoading, loginRequest]);
|
||||||
|
|
||||||
const connectUsingPhoneNumber = async () => {
|
const connectUsingPhoneNumber = () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
sendAuthSMS({
|
||||||
await sendAuthSMS({
|
smsType: "C",
|
||||||
smsType: "C",
|
body: "Se connecter sur Alerte-Secours:\nCode: [CODE]\n💙", // must don't exceed 160 chars including replaced [CODE]
|
||||||
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 });
|
||||||
|
|
@ -100,20 +93,39 @@ export default function AccountManagementModalConnect({
|
||||||
const [loginConfirmRequest] = useMutation(LOGIN_CONFIRM_MUTATION);
|
const [loginConfirmRequest] = useMutation(LOGIN_CONFIRM_MUTATION);
|
||||||
|
|
||||||
const confirmLoginRequest = useCallback(async () => {
|
const confirmLoginRequest = useCallback(async () => {
|
||||||
const deviceUuid = await getDeviceUuid();
|
try {
|
||||||
const {
|
const deviceUuid = await getDeviceUuid();
|
||||||
data: {
|
const {
|
||||||
doAuthLoginConfimLoginRequest: { authTokenJwt },
|
data: {
|
||||||
},
|
doAuthLoginConfimLoginRequest: { authTokenJwt },
|
||||||
} = await loginConfirmRequest({
|
},
|
||||||
variables: { loginRequestId: loginRequest.id, deviceUuid },
|
} = await loginConfirmRequest({
|
||||||
});
|
variables: { loginRequestId: loginRequest.id, deviceUuid },
|
||||||
await authActions.confirmLoginRequest({ authTokenJwt, isConnected });
|
});
|
||||||
}, [loginConfirmRequest, loginRequest?.id, isConnected]);
|
await authActions.confirmLoginRequest({ authTokenJwt, isConnected });
|
||||||
|
setIsLoading(false);
|
||||||
|
clearAuthWaitParams?.();
|
||||||
|
closeModal();
|
||||||
|
} catch (e) {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
loginConfirmRequest,
|
||||||
|
loginRequest?.id,
|
||||||
|
isConnected,
|
||||||
|
clearAuthWaitParams,
|
||||||
|
closeModal,
|
||||||
|
]);
|
||||||
|
|
||||||
const rejectLoginRequest = useCallback(async () => {
|
const rejectLoginRequest = useCallback(async () => {
|
||||||
await deleteLoginRequest({ variables: { id: loginRequest.id } });
|
try {
|
||||||
}, [deleteLoginRequest, loginRequest]);
|
await deleteLoginRequest({ variables: { id: loginRequest.id } });
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
clearAuthWaitParams?.();
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
}, [deleteLoginRequest, loginRequest?.id, clearAuthWaitParams, closeModal]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ export default function Form({
|
||||||
profileData,
|
profileData,
|
||||||
openAccountModal,
|
openAccountModal,
|
||||||
waitingSmsType,
|
waitingSmsType,
|
||||||
|
clearAuthWaitParams,
|
||||||
}) {
|
}) {
|
||||||
const { userId } = useSessionState(["userId"]);
|
const { userId } = useSessionState(["userId"]);
|
||||||
|
|
||||||
|
|
@ -153,7 +154,11 @@ export default function Form({
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PhoneNumbers data={profileData} waitingSmsType={waitingSmsType} />
|
<PhoneNumbers
|
||||||
|
data={profileData}
|
||||||
|
waitingSmsType={waitingSmsType}
|
||||||
|
clearAuthWaitParams={clearAuthWaitParams}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
|
@ -190,6 +195,7 @@ 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, Alert } from "react-native";
|
import { View } from "react-native";
|
||||||
|
|
||||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||||
|
|
||||||
|
|
@ -26,7 +26,11 @@ import {
|
||||||
|
|
||||||
import useSendAuthSMS from "~/hooks/useSendAuthSMS";
|
import useSendAuthSMS from "~/hooks/useSendAuthSMS";
|
||||||
|
|
||||||
export default function PhoneNumbersView({ data, waitingSmsType }) {
|
export default function PhoneNumbersView({
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
@ -41,18 +45,10 @@ export default function PhoneNumbersView({ data, waitingSmsType }) {
|
||||||
|
|
||||||
const registerPhoneNumber = useCallback(async () => {
|
const registerPhoneNumber = useCallback(async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
sendAuthSMS({
|
||||||
await sendAuthSMS({
|
smsType: "R",
|
||||||
smsType: "R",
|
body: "S'enregistrer sur Alerte-Secours:\nCode: [CODE]\n💙", // must don't exceed 160 chars including replaced [CODE]
|
||||||
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
|
||||||
|
|
@ -77,8 +73,16 @@ export default function PhoneNumbersView({ data, waitingSmsType }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data.selectOneUser.oneUserLoginRequest) {
|
if (data.selectOneUser.oneUserLoginRequest) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
clearAuthWaitParams?.();
|
||||||
}
|
}
|
||||||
}, [data.selectOneUser.oneUserLoginRequest]);
|
}, [data.selectOneUser.oneUserLoginRequest, clearAuthWaitParams]);
|
||||||
|
|
||||||
|
// 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, AppState } from "react-native";
|
import { ScrollView, View } from "react-native";
|
||||||
|
|
||||||
import Loader from "~/components/Loader";
|
import Loader from "~/components/Loader";
|
||||||
import { useSubscription } from "@apollo/client";
|
import { useSubscription } from "@apollo/client";
|
||||||
|
|
@ -12,7 +12,6 @@ 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";
|
||||||
|
|
||||||
|
|
@ -38,38 +37,12 @@ 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]);
|
||||||
|
|
||||||
useFocusEffect(
|
const clearAuthWaitParams = React.useCallback(() => {
|
||||||
React.useCallback(() => {
|
navigation.setParams({
|
||||||
restart();
|
waitingSmsType: undefined,
|
||||||
}, [restart]),
|
openAccountModal: undefined,
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const sub = AppState.addEventListener("change", (state) => {
|
|
||||||
if (state === "active") {
|
|
||||||
restart();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return () => {
|
}, [navigation]);
|
||||||
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 />;
|
||||||
|
|
@ -87,6 +60,7 @@ 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