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) && }