Ver código fonte

background location test

Viktoriia 5 meses atrás
pai
commit
fec7ba6614

+ 12 - 3
app.config.ts

@@ -70,7 +70,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
       googleMapsApiKey: env.IOS_GOOGLE_MAP_APIKEY
     },
     infoPlist: {
-      UIBackgroundModes: ['fetch'],
+      UIBackgroundModes: ['location', 'fetch'],
       NSLocationAlwaysUsageDescription:
         'Turn on location service to allow NomadMania.com find friends nearby.',
       NSPhotoLibraryUsageDescription:
@@ -83,6 +83,8 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
         'Nomadmania app needs access to the documents folder to select files.',
       NSCameraUsageDescription: 'Nomadmania app needs access to the camera to record video.',
       NSLocationWhenInUseUsageDescription:
+        'NomadMania app needs access to your location to show relevant data.',
+      NSLocationAlwaysAndWhenInUseUsageDescription:
         'NomadMania app needs access to your location to show relevant data.'
     },
     privacyManifests: {
@@ -103,7 +105,7 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
     },
     googleServicesFile: './google-services.json',
     permissions: [
-      // 'ACCESS_BACKGROUND_LOCATION',
+      'ACCESS_BACKGROUND_LOCATION',
       'ACCESS_FINE_LOCATION',
       'ACCESS_COARSE_LOCATION',
       'READ_EXTERNAL_STORAGE',
@@ -159,6 +161,13 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
         microphonePermission: 'Allow Nomadmania to access your microphone.'
       }
     ],
-    ['@maplibre/maplibre-react-native']
+    ['@maplibre/maplibre-react-native'],
+    [
+      'expo-location',
+      {
+        isIosBackgroundLocationEnabled: true,
+        isAndroidBackgroundLocationEnabled: true
+      }
+    ]
   ]
 });

+ 1 - 0
package.json

@@ -47,6 +47,7 @@
     "expo-splash-screen": "~0.27.5",
     "expo-sqlite": "^14.0.6",
     "expo-status-bar": "~1.12.1",
+    "expo-task-manager": "^11.8.2",
     "expo-updates": "~0.25.24",
     "formik": "^2.4.5",
     "moment": "^2.29.4",

+ 76 - 19
src/screens/InAppScreens/MapScreen/index.tsx

@@ -83,6 +83,10 @@ import MapButton from 'src/components/MapButton';
 import { useAvatarStore } from 'src/stores/avatarVersionStore';
 import _ from 'lodash';
 import ScaleBar from 'src/components/ScaleBar';
+import {
+  startBackgroundLocationUpdates,
+  stopBackgroundLocationUpdates
+} from 'src/utils/backgroundLocation';
 
 const defaultUserAvatar = require('assets/icon-user-share-location-solid.png');
 const logo = require('assets/logo-ua.png');
@@ -621,9 +625,24 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
       ) {
         setShowNomads(false);
         storage.set('showNomads', false);
+        await stopBackgroundLocationUpdates();
         return;
       }
 
+      const bgStatus = await Location.getBackgroundPermissionsAsync();
+      if (bgStatus.status !== 'granted') {
+        const { status } = await Location.requestBackgroundPermissionsAsync();
+        if (status === Location.PermissionStatus.GRANTED) {
+          await startBackgroundLocationUpdates();
+          console.log('[Permissions] Background granted');
+        } else {
+          console.log('[Permissions] Background denied');
+        }
+      } else {
+        await startBackgroundLocationUpdates();
+        console.log('[Permissions] Background already granted');
+      }
+
       try {
         let currentLocation = await Location.getCurrentPositionAsync({
           accuracy: Location.Accuracy.Balanced
@@ -724,10 +743,10 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
             animationDuration: 500
           });
         } else {
-          console.warn("Camera ref is not available.");
+          console.warn('Camera ref is not available.');
         }
       }, 500);
-  
+
       return () => clearTimeout(timeoutId);
     }
   }, [initialRegion]);
@@ -894,6 +913,19 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
 
       if (status === 'granted' && isServicesEnabled) {
         await getLocation();
+        const bgStatus = await Location.getBackgroundPermissionsAsync();
+        if (bgStatus.status !== 'granted') {
+          const { status } = await Location.requestBackgroundPermissionsAsync();
+          if (status === Location.PermissionStatus.GRANTED) {
+            await startBackgroundLocationUpdates();
+            console.log('[Permissions] Background granted');
+          } else {
+            console.log('[Permissions] Background denied');
+          }
+        } else {
+          await startBackgroundLocationUpdates();
+          console.log('[Permissions] Background already granted');
+        }
       } else if (!canAskAgain || !isServicesEnabled) {
         setOpenSettingsVisible(true);
       } else {
@@ -1397,12 +1429,18 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
                   'interpolate',
                   ['linear'],
                   ['get', 'point_count'],
-                  0, 15,
-                  10, 17,
-                  20, 19,
-                  50, 21,
-                  75, 23,
-                  100, 25
+                  0,
+                  15,
+                  10,
+                  17,
+                  20,
+                  19,
+                  50,
+                  21,
+                  75,
+                  23,
+                  100,
+                  25
                 ],
                 circleColor: 'rgba(237, 147, 52, 1)',
                 circleStrokeWidth: 2,
@@ -1421,9 +1459,12 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
                   'interpolate',
                   ['linear'],
                   ['get', 'point_count'],
-                  0, 13,
-                  20, 14,
-                  75, 15
+                  0,
+                  13,
+                  20,
+                  14,
+                  75,
+                  15
                 ],
                 textColor: '#FFFFFF',
                 textHaloColor: '#000000',
@@ -1443,11 +1484,16 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
                   'interpolate',
                   ['linear'],
                   ['zoom'],
-                  0, 0.24,
-                  5, 0.28,
-                  10, 0.33,
-                  15, 0.38,
-                  20, 0.42
+                  0,
+                  0.24,
+                  5,
+                  0.28,
+                  10,
+                  0.33,
+                  15,
+                  0.38,
+                  20,
+                  0.42
                 ],
                 iconAllowOverlap: true
               }}
@@ -1483,7 +1529,12 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
       </MapLibreGL.MapView>
 
       {center ? (
-        <ScaleBar zoom={zoom} latitude={center[1]} isVisible={isZooming} bottom={tabBarHeight + 80} />
+        <ScaleBar
+          zoom={zoom}
+          latitude={center[1]}
+          isVisible={isZooming}
+          bottom={tabBarHeight + 80}
+        />
       ) : null}
 
       {regionPopupVisible && regionData ? (
@@ -1569,7 +1620,9 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
                 userInfoData?.avatar ? (
                   <Image
                     style={styles.avatar}
-                    source={{ uri: API_HOST + '/img/avatars/' + userInfoData?.avatar + '?v=' + avatarVersion }}
+                    source={{
+                      uri: API_HOST + '/img/avatars/' + userInfoData?.avatar + '?v=' + avatarVersion
+                    }}
                   />
                 ) : (
                   <AvatarWithInitials
@@ -1622,7 +1675,11 @@ const MapScreen: any = ({ navigation, route }: { navigation: any; route: any })
             <ScrollView
               horizontal
               showsHorizontalScrollIndicator={false}
-              contentContainerStyle={{ paddingHorizontal: 12, gap: isSmallScreen ? 8 : 12, flexDirection: 'row' }}
+              contentContainerStyle={{
+                paddingHorizontal: 12,
+                gap: isSmallScreen ? 8 : 12,
+                flexDirection: 'row'
+              }}
             >
               <MapButton
                 onPress={() => {

+ 86 - 0
src/utils/backgroundLocation.ts

@@ -0,0 +1,86 @@
+import * as TaskManager from 'expo-task-manager';
+import * as Location from 'expo-location';
+import axios from 'axios';
+import { storage, StoreType } from 'src/storage';
+import { Platform } from 'react-native';
+import { API_URL } from 'src/constants';
+import { API } from 'src/types';
+
+const LOCATION_TASK_NAME = 'BACKGROUND_LOCATION_TASK';
+
+TaskManager.defineTask(LOCATION_TASK_NAME, async ({ data, error }) => {
+  if (error) {
+    console.error('[BackgroundLocation] Task error:', error);
+    return;
+  }
+  if (data) {
+    const { locations } = data as any;
+    if (locations && locations.length > 0) {
+      const { coords, timestamp } = locations[0];
+
+      console.log('[BackgroundLocation] New location:', coords);
+      const token = storage.get('token', StoreType.STRING);
+
+      if (!token) {
+        return;
+      }
+
+      try {
+        const response = await axios.postForm(
+          API_URL + '/' + API.UPDATE_LOCATION,
+          {
+            token,
+            lat: coords.latitude,
+            lng: coords.longitude,
+            background: LOCATION_TASK_NAME,
+            location_timestamp: timestamp
+          },
+          {
+            headers: {
+              Platform: Platform.OS
+            }
+          }
+        );
+        console.log('[BackgroundLocation] Location sent:', response.data);
+      } catch (sendError) {
+        console.error('[BackgroundLocation] Sending location failed:', sendError);
+      }
+    }
+  }
+  console.log('[BackgroundLocation] TaskManager end');
+});
+
+export const startBackgroundLocationUpdates = async () => {
+  const hasStarted = await Location.hasStartedLocationUpdatesAsync(LOCATION_TASK_NAME);
+  if (hasStarted) {
+    console.log('[BackgroundLocation] Already started...');
+    return;
+  }
+
+  await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
+    accuracy: Location.Accuracy.Highest,
+    // 2 hours for Android
+    timeInterval: 2 * 60 * 60 * 1000,
+    // just for testing on iOS
+    showsBackgroundLocationIndicator: true,
+    pausesUpdatesAutomatically: false,
+    // banner on Android
+    foregroundService: {
+      notificationTitle: 'NomadMania tracking your location',
+      notificationBody: 'Location is used in background every 2 hours.',
+      notificationColor: '#0F3F4F'
+    },
+    // iOS only
+    activityType: Location.ActivityType.Other
+  });
+
+  console.log('[BackgroundLocation] Started task');
+};
+
+export const stopBackgroundLocationUpdates = async () => {
+  const hasStarted = await Location.hasStartedLocationUpdatesAsync(LOCATION_TASK_NAME);
+  if (hasStarted) {
+    await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME);
+    console.log('[BackgroundLocation] Stopped task');
+  }
+};