diff --git a/src/app/subscriptions/alerting/gql.js b/src/app/subscriptions/alerting/gql.js
index 0b4f570..6e2f93c 100644
--- a/src/app/subscriptions/alerting/gql.js
+++ b/src/app/subscriptions/alerting/gql.js
@@ -25,6 +25,8 @@ const ALERT_FIELDS_FRAGMENT = gql`
acknowledgedRelativeCount
acknowledgedAroundCount
acknowledgedConnectCount
+ followLocation
+ followLocationRan
accessCode
userId
keepOpenAt
diff --git a/src/misc/devicePrefs.js b/src/misc/devicePrefs.js
index 3d830cd..7d90aaf 100644
--- a/src/misc/devicePrefs.js
+++ b/src/misc/devicePrefs.js
@@ -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;
diff --git a/src/scenes/AlertCurOverview/FieldFollowLocation.js b/src/scenes/AlertCurOverview/FieldFollowLocation.js
new file mode 100644
index 0000000..c3f64f3
--- /dev/null
+++ b/src/scenes/AlertCurOverview/FieldFollowLocation.js
@@ -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 (
+
+
+
+ Suivre ma localisation
+
+
+
+ {showPermissionWarning && (
+
+
+
+
+ Permissions de localisation requises
+
+
+
+
+ )}
+
+ );
+}
+
+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 }),
+ },
+ }),
+);
diff --git a/src/scenes/AlertCurOverview/gql.js b/src/scenes/AlertCurOverview/gql.js
index 206ca47..da85cb5 100644
--- a/src/scenes/AlertCurOverview/gql.js
+++ b/src/scenes/AlertCurOverview/gql.js
@@ -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
+ }
+ }
+`;
diff --git a/src/scenes/AlertCurOverview/index.js b/src/scenes/AlertCurOverview/index.js
index ac7f1db..6706878 100644
--- a/src/scenes/AlertCurOverview/index.js
+++ b/src/scenes/AlertCurOverview/index.js
@@ -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}
/>
+
+
)}
@@ -505,7 +508,7 @@ export default withConnectivity(
)}
-
+ {!isSent && }
{(!isSent || !isOpen) && }