Browse Source

Add location permission request

master
Devin Dooley 1 year ago
parent
commit
98d5456b73
7 changed files with 335 additions and 1 deletions
  1. +1
    -0
      clients/mobile/android/gradle.properties
  2. +67
    -1
      clients/mobile/features/App.js
  3. +37
    -0
      clients/mobile/features/location/Locations.js
  4. +174
    -0
      clients/mobile/package-lock.json
  5. +3
    -0
      clients/mobile/package.json
  6. +45
    -0
      clients/shared/store/slices/locationSlice.js
  7. +8
    -0
      clients/shared/store/store.js

+ 1
- 0
clients/mobile/android/gradle.properties View File

@ -19,3 +19,4 @@
android.useAndroidX=true
android.enableJetifier=true
AsyncStorage_db_size_in_MB=250

+ 67
- 1
clients/mobile/features/App.js View File

@ -1,4 +1,4 @@
import * as React from 'react';
import React, { useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
@ -6,6 +6,8 @@ import { createDrawerNavigator } from '@react-navigation/drawer';
import { useSelector, useDispatch } from 'react-redux';
import { FontAwesome } from '@expo/vector-icons';
import { View, TouchableOpacity } from 'react-native';
import * as Location from 'expo-location';
import * as TaskManager from 'expo-task-manager';
import Home from './home/Home';
import Weather from './weather/Weather';
@ -14,9 +16,73 @@ import Settings from './settings/Settings';
import Login from './auth/Login';
import SignUp from './auth/SignUp';
import { selectAuthenticated, logout } from 'shared/store/slices/authSlice';
import { logLocations, logLocationsError } from 'shared/store/slices/locationSlice';
const backgroundLocationTaskName = 'backgroundLocationTask';
const backgroundLocationOptions = {
accuracy: Location.Accuracy.Highest,
timeInterval: 1000 * 60 * 2, // 2 minutes
distanceInterval: 10, // 10 meters
deferredUpdatesInterval: 0,
deferredUpdatesDistance: 0,
showsBackgroundLocationIndicator: false,
foregroundService: {
notificationTitle: 'Me',
notificationBody: 'Collecting your location data for you :)',
notificationColor: '#FFFFFF',
},
pausesUpdatesAutomatically: false,
activityType: Location.ActivityType.Other,
};
// Register the task for background location tracking
// Per the TaskManager docs, this needs to be defined in the global scope
// of the javascript bundle, since this task will be spun up on-demand in
// the background without loading the app itself
TaskManager.defineTask(backgroundLocationTaskName, ({ data: { locations }, error }) => {
const dispatch = useDispatch();
if (error) {
dispatch(logLocationsError(error.message));
}
dispatch(logLocations(locations));
});
function App() {
const authenticated = useSelector(selectAuthenticated);
useEffect(() => {
async function handleLocationPermissions() {
if (!authenticated) {
return
}
let locationPermissions = await Location.getPermissionsAsync();
if (locationPermissions.status !== 'granted') {
if (!locationPermissions.canAskAgain) {
dispatch(logLocationsError('Please manually grant location permissions to ' +
'use the location tracking feature'));
} else {
locationPermissions = await Location.requestPermissionsAsync();
if (locationPermissions.status !== 'granted') {
dispatch(logLocationsError('Location permissions not granted'));
}
}
}
if (
locationPermissions.status === 'granted' &&
!Location.hasStartedLocationUpdatesAsync(backgroundLocationTaskName)
) {
Location.startLocationUpdatesAsync(backgroundLocationTaskName, backgroundLocationOptions);
}
}
handleLocationPermissions();
});
return (
<NavigationContainer>
{ authenticated ? authenticatedNavigator() : unauthenticatedNavigator() }


+ 37
- 0
clients/mobile/features/location/Locations.js View File

@ -0,0 +1,37 @@
import React, { useCallback, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { View, Button, Text } from 'react-native';
import MapboxGL from '@react-native-mapbox-gl/maps';
function Location(props) {
useEffect(() => {
MapboxGL.setAccessToken(process.env.MAPBOX_API_KEY);
MapboxGL.setTelemetryEnabled(false); // Give them less data
});
const locationsLog = useSelector(selectLocationLog);
return (
<View style={ locationStyle }>
<MapboxGL.HeatmapLayer
/>
<Button
variant="light"
onClick={ () => uploadLocations(locationsLog.locations) }
disabled={ !locationsLog.location.length === 0 || locationsLog.error }
>
</Button>
{ locationsLogError && <Text>{ locationsLogError }</Text> }
</View>
)
}
const locationStyle = {
backgroundColor: 'white',
flex: 1,
justifyContent: 'center',
paddingHorizontal: 10,
};
export default Location;

+ 174
- 0
clients/mobile/package-lock.json View File

@ -2721,6 +2721,19 @@
}
}
},
"@mapbox/geo-viewport": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@mapbox/geo-viewport/-/geo-viewport-0.4.0.tgz",
"integrity": "sha512-5OAXoP8C96Co3UDIzFliSzrK5njb9oG2H/RTMaZUW1swxIZlj3n3A5ccep5XAEIhTWxBLg/Cz48D3rUbAdXwHQ==",
"requires": {
"@mapbox/sphericalmercator": "~1.1.0"
}
},
"@mapbox/sphericalmercator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@mapbox/sphericalmercator/-/sphericalmercator-1.1.0.tgz",
"integrity": "sha512-pEsfZyG4OMThlfFQbCte4gegvHUjxXCjz0KZ4Xk8NdOYTQBLflj6U8PL05RPAiuRAMAQNUUKJuL6qYZ5Y4kAWA=="
},
"@react-native-community/async-storage": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/@react-native-community/async-storage/-/async-storage-1.11.0.tgz",
@ -3008,6 +3021,20 @@
"resolved": "https://registry.npmjs.org/@react-native-community/masked-view/-/masked-view-0.1.10.tgz",
"integrity": "sha512-rk4sWFsmtOw8oyx8SD3KSvawwaK7gRBSEIy2TAwURyGt+3TizssXP1r8nx3zY+R7v2vYYHXZ+k2/GULAT/bcaQ=="
},
"@react-native-mapbox-gl/maps": {
"version": "8.1.0-rc.2",
"resolved": "https://registry.npmjs.org/@react-native-mapbox-gl/maps/-/maps-8.1.0-rc.2.tgz",
"integrity": "sha512-gjU5YSjlxqWZ5flMxiEoKUsu9DjLy3aqntivJGbqooRT8d1wILtErXDG2TDyprWGrUpFUl/D27TjLLSIhVmyOw==",
"requires": {
"@mapbox/geo-viewport": ">= 0.4.0",
"@turf/along": ">= 4.0.0 <7.0.0",
"@turf/distance": ">= 4.0.0 <7.0.0",
"@turf/helpers": ">= 4.6.0 <7.0.0",
"@turf/length": ">= 4.6.0 <7.0.0",
"@turf/nearest-point-on-line": ">= 4.0.0 <7.0.0",
"debounce": "^1.2.0"
}
},
"@react-navigation/bottom-tabs": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-5.6.1.tgz",
@ -3085,6 +3112,121 @@
"type-detect": "4.0.8"
}
},
"@turf/along": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@turf/along/-/along-6.0.1.tgz",
"integrity": "sha512-6PptAcrsFR3o0Flpktk8Vo68W2txEVTh14zjoTVu+H5docd2+pv5/upA77bg3YFBoJgAxmUFt1leDdjReJ44BQ==",
"requires": {
"@turf/bearing": "6.x",
"@turf/destination": "6.x",
"@turf/distance": "6.x",
"@turf/helpers": "6.x",
"@turf/invariant": "6.x"
}
},
"@turf/bbox": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.0.1.tgz",
"integrity": "sha512-EGgaRLettBG25Iyx7VyUINsPpVj1x3nFQFiGS3ER8KCI1MximzNLsam3eXRabqQDjyAKyAE1bJ4EZEpGvspQxw==",
"requires": {
"@turf/helpers": "6.x",
"@turf/meta": "6.x"
}
},
"@turf/bearing": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@turf/bearing/-/bearing-6.0.1.tgz",
"integrity": "sha512-mXY1NozqV9EFfBTbUItujwfqfQF0G/Xe2fzvnZle90ekPEUfhi4Dgf5JswJTd96J9LiT8kcd6Jonp5khnx0wIg==",
"requires": {
"@turf/helpers": "6.x",
"@turf/invariant": "6.x"
}
},
"@turf/destination": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@turf/destination/-/destination-6.0.1.tgz",
"integrity": "sha512-MroK4nRdp7as174miCAugp8Uvorhe6rZ7MJiC9Hb4+hZR7gNFJyVKmkdDDXIoCYs6MJQsx0buI+gsCpKwgww0Q==",
"requires": {
"@turf/helpers": "6.x",
"@turf/invariant": "6.x"
}
},
"@turf/distance": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@turf/distance/-/distance-6.0.1.tgz",
"integrity": "sha512-q7t7rWIWfkg7MP1Vt4uLjSEhe5rPfCO2JjpKmk7JC+QZKEQkuvHEqy3ejW1iC7Kw5ZcZNR3qdMGGz+6HnVwqvg==",
"requires": {
"@turf/helpers": "6.x",
"@turf/invariant": "6.x"
}
},
"@turf/helpers": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.1.4.tgz",
"integrity": "sha512-vJvrdOZy1ngC7r3MDA7zIGSoIgyrkWcGnNIEaqn/APmw+bVLF2gAW7HIsdTxd12s5wQMqEpqIQrmrbRRZ0xC7g=="
},
"@turf/invariant": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.1.2.tgz",
"integrity": "sha512-WU08Ph8j0J2jVGlQCKChXoCtI50BB3yEH21V++V0T4cR1T27HKCxkehV2sYMwTierfMBgjwSwDIsxnR4/2mWXg==",
"requires": {
"@turf/helpers": "6.x"
}
},
"@turf/length": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@turf/length/-/length-6.0.2.tgz",
"integrity": "sha512-nyfXMowVtX2dICEG7u7EGC2SMaauVUWIMc9eWQrEauNA/9aw+7wbiuip4GPBoyeXEUUekF0EOjJn5aB9Zc8CzA==",
"requires": {
"@turf/distance": "6.x",
"@turf/helpers": "6.x",
"@turf/meta": "6.x"
}
},
"@turf/line-intersect": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-6.0.2.tgz",
"integrity": "sha512-pfL/lBu7ukBPdTjvSCmcNUzZ83V4R95htwqs5NqU8zeS4R+5KTwacbrOYKztjpmHBwUmPEIIpSbqkUoD0Fp7kg==",
"requires": {
"@turf/helpers": "6.x",
"@turf/invariant": "6.x",
"@turf/line-segment": "6.x",
"@turf/meta": "6.x",
"geojson-rbush": "3.x"
}
},
"@turf/line-segment": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@turf/line-segment/-/line-segment-6.0.2.tgz",
"integrity": "sha512-8AkzDHoNw3X68y115josal+lvdAi4b2P1K0YNTKGyLRBaUhPXVSuMBpMd53FRF1hYEb9UJgMbugF9ZE7m5L6zg==",
"requires": {
"@turf/helpers": "6.x",
"@turf/invariant": "6.x",
"@turf/meta": "6.x"
}
},
"@turf/meta": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.0.2.tgz",
"integrity": "sha512-VA7HJkx7qF1l3+GNGkDVn2oXy4+QoLP6LktXAaZKjuT1JI0YESat7quUkbCMy4zP9lAUuvS4YMslLyTtr919FA==",
"requires": {
"@turf/helpers": "6.x"
}
},
"@turf/nearest-point-on-line": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@turf/nearest-point-on-line/-/nearest-point-on-line-6.0.2.tgz",
"integrity": "sha512-re9tSEwYyG1EMo4ObXXAKYVhkMVANPIIJQ1V/J1lKcNfHVc1pYmd3D5jkTNBh+oriI9VL4PVML3eqYE+Zcg0mg==",
"requires": {
"@turf/bearing": "6.x",
"@turf/destination": "6.x",
"@turf/distance": "6.x",
"@turf/helpers": "6.x",
"@turf/invariant": "6.x",
"@turf/line-intersect": "6.x",
"@turf/meta": "6.x"
}
},
"@types/babel__core": {
"version": "7.1.9",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz",
@ -5136,6 +5278,14 @@
"lodash": "^4.17.15"
}
},
"expo-task-manager": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/expo-task-manager/-/expo-task-manager-8.3.0.tgz",
"integrity": "sha512-hE7DJ/DrCmMQk43erM537UjzAcuLZkwMKMMkuhmnu6gpdjkkGERGUX5Jx8AWmrspVOsK/CO1BZp74+3LJj4sMg==",
"requires": {
"unimodules-app-loader": "~1.2.0"
}
},
"expo-updates": {
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/expo-updates/-/expo-updates-0.2.10.tgz",
@ -5586,6 +5736,17 @@
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
"integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg=="
},
"geojson-rbush": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/geojson-rbush/-/geojson-rbush-3.1.2.tgz",
"integrity": "sha512-grkfdg3HIeTjwTfiJe5FT8+fGU3fABCc+vRJDBwdQz9kkLF0Sbif2gs2JUzjewwgmnvLGy9fInySDeADoNuk7w==",
"requires": {
"@turf/bbox": "*",
"@turf/helpers": "6.x",
"@turf/meta": "6.x",
"rbush": "^2.0.0"
}
},
"get-caller-file": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
@ -11315,11 +11476,24 @@
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz",
"integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA=="
},
"quickselect": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-1.1.1.tgz",
"integrity": "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ=="
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"rbush": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/rbush/-/rbush-2.0.2.tgz",
"integrity": "sha512-XBOuALcTm+O/H8G90b6pzu6nX6v2zCKiFG4BJho8a+bY6AER6t8uQUZdi5bomQc0AprCWhEGa7ncAbbRap0bRA==",
"requires": {
"quickselect": "^1.0.1"
}
},
"react": {
"version": "16.11.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.11.0.tgz",


+ 3
- 0
clients/mobile/package.json View File

@ -11,6 +11,7 @@
"@babel/runtime": "^7.10.3",
"@react-native-community/async-storage": "~1.11.0",
"@react-native-community/masked-view": "0.1.10",
"@react-native-mapbox-gl/maps": "^8.1.0-rc.2",
"@react-navigation/bottom-tabs": "^5.6.1",
"@react-navigation/drawer": "^5.8.5",
"@react-navigation/native": "^5.6.1",
@ -18,7 +19,9 @@
"@reduxjs/toolkit": "^1.4.0",
"babel-plugin-inline-dotenv": "^1.5.0",
"expo": "^38.0.0",
"expo-location": "~8.2.1",
"expo-splash-screen": "~0.3.1",
"expo-task-manager": "~8.3.0",
"expo-updates": "~0.2.8",
"react": "16.11.0",
"react-dom": "16.11.0",


+ 45
- 0
clients/shared/store/slices/locationSlice.js View File

@ -0,0 +1,45 @@
import { createSlice } from '@reduxjs/toolkit';
import api from 'shared/api';
export const selectLocationHistory = state => state.location.history;
export const selectLocationLog = state => state.location.log;
export const locationSlice = createSlice({
name: 'location',
initialState: {
// Location history pulled from server for display
history: {
locations: [],
error: '',
},
// Local location log collected by native app
log: {
locations: [],
error: '',
},
},
reducers: {
logLocations: (state, action) => {
state.location.log.locations = state.location.log.locations.concat(action.payload);
},
logLocationsError: (state, action) => {
state.location.log.error = action.payload;
},
setHistory: (state, action) => {
const { locations, error } = action.payload;
state.location.history.locations = locations;
state.location.history.error = error;
},
},
});
export const logLocations = (locs) => dispatch => {
dispatch(locationSlice.actions.logLocation(locs));
};
export const logLocationsError = (err) => dispatch => {
dispatch(locationSlice.actions.logLocationsError(err));
};
export default locationSlice.reducer;

+ 8
- 0
clients/shared/store/store.js View File

@ -5,6 +5,7 @@ import thunk from 'redux-thunk';
import authSlice from 'shared/store/slices/authSlice';
import bankingSlice from 'shared/store/slices/bankingSlice';
import locationSlice from 'shared/store/slices/locationSlice';
import settingsSlice from 'shared/store/slices/settingsSlice';
import weatherSlice from 'shared/store/slices/weatherSlice';
@ -17,9 +18,16 @@ function initStore(storage) {
storage,
};
const persistLocationConfig = {
key: 'location',
storage,
whitelist: ['log'],
};
const rootReducer = combineReducers({
auth: persistReducer(persistAuthConfig, authSlice),
banking: bankingSlice,
'location': locationSlice, // Location is a keyword defined on the global Window object
settings: settingsSlice,
weather: weatherSlice,
});


Loading…
Cancel
Save