feat(follow-location): wip

This commit is contained in:
devthejo 2025-05-24 16:15:50 +02:00
parent ca3a2c8fcc
commit c30c0b0482
5 changed files with 253 additions and 2 deletions

View file

@ -25,6 +25,8 @@ const ALERT_FIELDS_FRAGMENT = gql`
acknowledgedRelativeCount
acknowledgedAroundCount
acknowledgedConnectCount
followLocation
followLocationRan
accessCode
userId
keepOpenAt

View file

@ -1,5 +1,5 @@
// related to services/tasks/src/geocode/config.js
export const TRACK_MOVE = 100;
export const TRACK_MOVE = 10;
export const DEFAULT_DEVICE_RADIUS_ALL = 500;
export const DEFAULT_DEVICE_RADIUS_REACH = 25000;
export const MAX_BASEUSER_DEVICE_TRACKING = 25000;

View file

@ -0,0 +1,232 @@
import { useCallback, useEffect, useState, useRef } from "react";
import { View, AppState } from "react-native";
import { Switch, Button } from "react-native-paper";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import { useMutation } from "@apollo/client";
import * as Location from "expo-location";
import { UPDATE_ALERT_FOLLOW_LOCATION_MUTATION } from "./gql";
import Text from "~/components/Text";
import { createStyles, useTheme } from "~/theme";
import { usePermissionsState, permissionsActions } from "~/stores";
import requestPermissionLocationBackground from "~/permissions/requestPermissionLocationBackground";
import requestPermissionLocationForeground from "~/permissions/requestPermissionLocationForeground";
import openSettings from "~/lib/native/openSettings";
export default function FieldFollowLocation({ alert }) {
const styles = useStyles();
const { colors } = useTheme();
const { id: alertId } = alert;
const [updateAlertFollowLocationMutation] = useMutation(
UPDATE_ALERT_FOLLOW_LOCATION_MUTATION,
);
const { locationForeground, locationBackground } = usePermissionsState([
"locationForeground",
"locationBackground",
]);
const [followLocation, setFollowLocation] = useState(
alert.followLocation || false,
);
const [isRequestingPermissions, setIsRequestingPermissions] = useState(false);
const prevFollowLocation = useRef(alert.followLocation);
useEffect(() => {
if (prevFollowLocation.current !== alert.followLocation) {
prevFollowLocation.current = alert.followLocation;
setFollowLocation(alert.followLocation || false);
}
}, [alert.followLocation]);
// Check current permission status
const checkPermissionStatus = useCallback(async () => {
try {
const { status: fgStatus } =
await Location.getForegroundPermissionsAsync();
const { status: bgStatus } =
await Location.getBackgroundPermissionsAsync();
permissionsActions.setLocationForeground(fgStatus === "granted");
permissionsActions.setLocationBackground(bgStatus === "granted");
} catch (error) {
console.error("Error checking location permissions:", error);
}
}, []);
// Check permissions on mount
useEffect(() => {
checkPermissionStatus();
}, [checkPermissionStatus]);
// Listen for app state changes to refresh permissions when returning from settings
useEffect(() => {
const handleAppStateChange = (nextAppState) => {
if (nextAppState === "active") {
// App came to foreground, check permissions
checkPermissionStatus();
}
};
const subscription = AppState.addEventListener(
"change",
handleAppStateChange,
);
return () => {
subscription?.remove();
};
}, [checkPermissionStatus]);
const requestLocationPermissions = useCallback(async () => {
setIsRequestingPermissions(true);
try {
// Request foreground permission first
const foregroundGranted = await requestPermissionLocationForeground();
permissionsActions.setLocationForeground(foregroundGranted);
if (foregroundGranted) {
// Request background permission if foreground is granted
const backgroundGranted = await requestPermissionLocationBackground();
permissionsActions.setLocationBackground(backgroundGranted);
return backgroundGranted;
}
return false;
} catch (error) {
console.error("Error requesting location permissions:", error);
return false;
} finally {
setIsRequestingPermissions(false);
}
}, []);
const toggleFollowLocation = useCallback(
async (value) => {
if (value) {
// Check if permissions are granted when enabling
if (!locationForeground || !locationBackground) {
const permissionsGranted = await requestLocationPermissions();
if (!permissionsGranted) {
// Don't enable if permissions weren't granted
return;
}
}
}
setFollowLocation(value);
updateAlertFollowLocationMutation({
variables: { alertId, followLocation: value },
});
},
[
alertId,
updateAlertFollowLocationMutation,
locationForeground,
locationBackground,
requestLocationPermissions,
],
);
const hasRequiredPermissions = locationForeground && locationBackground;
const showPermissionWarning = followLocation && !hasRequiredPermissions;
return (
<View style={styles.container}>
<View style={styles.content}>
<MaterialCommunityIcons
name="crosshairs-gps"
style={styles.icon}
size={20}
/>
<Text style={styles.label}>Suivre ma localisation</Text>
<Switch
value={followLocation}
onValueChange={toggleFollowLocation}
color={colors.primary}
disabled={isRequestingPermissions}
/>
</View>
{showPermissionWarning && (
<View style={styles.warningContainer}>
<View style={styles.warningContent}>
<MaterialCommunityIcons
name="alert-circle-outline"
style={styles.warningIcon}
size={16}
/>
<Text style={styles.warningText}>
Permissions de localisation requises
</Text>
</View>
<Button
mode="outlined"
onPress={openSettings}
style={styles.settingsButton}
labelStyle={styles.settingsButtonLabel}
compact
>
Paramétrer
</Button>
</View>
)}
</View>
);
}
const useStyles = createStyles(
({ wp, hp, scaleText, fontSize, theme: { colors } }) => ({
container: {
marginVertical: hp(1),
paddingHorizontal: wp(4),
paddingVertical: hp(1.5),
backgroundColor: colors.surface,
borderRadius: 8,
borderWidth: 1,
borderColor: colors.outline,
},
content: {
flexDirection: "row",
alignItems: "center",
},
icon: {
color: colors.primary,
marginRight: wp(2),
},
label: {
flex: 1,
...scaleText({ fontSize: 16 }),
color: colors.onSurface,
},
warningContainer: {
marginTop: hp(1),
paddingTop: hp(1),
borderTopWidth: 1,
borderTopColor: colors.outline,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
},
warningContent: {
flexDirection: "row",
alignItems: "center",
flex: 1,
},
warningIcon: {
color: colors.error,
marginRight: wp(1),
},
warningText: {
...scaleText({ fontSize: 14 }),
color: colors.error,
flex: 1,
},
settingsButton: {
marginLeft: wp(2),
},
settingsButtonLabel: {
...scaleText({ fontSize: 12 }),
},
}),
);

View file

@ -60,3 +60,17 @@ export const UPDATE_ALERT_SUBJECT_MUTATION = gql`
}
}
`;
export const UPDATE_ALERT_FOLLOW_LOCATION_MUTATION = gql`
mutation updateAlertFollowLocation(
$alertId: Int!
$followLocation: Boolean!
) {
updateOneAlert(
pk_columns: { id: $alertId }
_set: { followLocation: $followLocation }
) {
id
}
}
`;

View file

@ -50,6 +50,7 @@ import { useMutation } from "@apollo/client";
import FieldLevel from "./FieldLevel";
import FieldSubject from "./FieldSubject";
import FieldFollowLocation from "./FieldFollowLocation";
import MapLinksButton from "~/containers/MapLinksButton";
import { useTheme } from "~/theme";
@ -497,6 +498,8 @@ export default withConnectivity(
alert={alert}
setParentScrollEnabled={setParentScrollEnabled}
/>
<FieldFollowLocation alert={alert} />
</View>
)}
@ -505,7 +508,7 @@ export default withConnectivity(
<AlertInfoLineLevel alert={alert} isFirst />
)}
<AlertInfoLineCode alert={alert} />
<AlertInfoLineDistance alert={alert} />
{!isSent && <AlertInfoLineDistance alert={alert} />}
<AlertInfoLineCreatedTime alert={alert} />
<AlertInfoLineClosedTime alert={alert} />
{(!isSent || !isOpen) && <AlertInfoLineSubject alert={alert} />}