Skip to content

Commit

Permalink
feat(#19): view current location as a driver i want to see my current…
Browse files Browse the repository at this point in the history
… location on a map in real time so that i can track my position and navigate the area effectively

     - modifying map screen ui
     - using auth token as channel name when publishing location
     - changes in store
     - test screen (token display)
  • Loading branch information
chriscoderdr committed Nov 2, 2024
1 parent fbe992e commit 691aafa
Show file tree
Hide file tree
Showing 29 changed files with 3,536 additions and 243 deletions.
12 changes: 9 additions & 3 deletions apps/api/src/mqtt-listener.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import jwt from 'jsonwebtoken';
import mqtt from 'mqtt';
import Driver from './models/driver';
import logger from './utils/logger';
Expand Down Expand Up @@ -50,7 +51,12 @@ mqttClient.on('reconnect', () => {
mqttClient.on('message', async (topic, message) => {
logger.info(`Received message on topic ${topic}: ${message.toString()}`);
try {
const driverId = topic.split('/')[2];
const driverAccessToken = topic.split('/')[2];
const ACCESS_TOKEN_SECRET =
process.env.ACCESS_TOKEN_SECRET || 'access-secret';
const decoded = jwt.verify(driverAccessToken, ACCESS_TOKEN_SECRET) as {
driverId: number;
};
const payload = JSON.parse(message.toString());
const { latitude, longitude, isAvailable } = payload;

Expand All @@ -62,11 +68,11 @@ mqttClient.on('message', async (topic, message) => {
isAvailable: isAvailable
},
{
where: { id: driverId }
where: { id: decoded.driverId }
}
);

logger.info(`Updated location for driver ${driverId}`);
logger.info(`Updated location for driver ${decoded.driverId}`);
} catch (error: any) {
logger.error('Error processing message: ' + error.message);
}
Expand Down
27 changes: 25 additions & 2 deletions apps/driver-app/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
// https://docs.expo.dev/guides/using-eslint/
module.exports = {
extends: 'expo',
extends: [
'expo',
'eslint:recommended',
'plugin:prettier/recommended'
],
plugins: ['prettier'],
rules: {
'prettier/prettier': [
'error',
{
trailingComma: 'none',
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
endOfLine: 'lf',
printWidth: 80,
proseWrap: 'never'
}
],
'no-trailing-spaces': [
'error',
{ skipBlankLines: false, ignoreComments: false }
]
}
};
13 changes: 10 additions & 3 deletions apps/driver-app/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"icon": "./assets/images/icon.png",
"scheme": "myapp",
"userInterfaceStyle": "automatic",
"associatedDomains": ["applinks:taxi.chriscoder.com"],
"associatedDomains": [
"applinks:taxi.chriscoder.com"
],
"splash": {
"image": "./assets/images/splash.png",
"resizeMode": "contain",
Expand All @@ -18,7 +20,11 @@
"supportsTablet": true,
"bundleIdentifier": "com.chriscoder.driverapp",
"infoPlist": {
"UIBackgroundModes": ["location", "fetch", "remote-notification"]
"UIBackgroundModes": [
"location",
"fetch",
"remote-notification"
]
}
},
"android": {
Expand Down Expand Up @@ -49,7 +55,8 @@
}
],
"expo-router",
"expo-font"
"expo-font",
"expo-secure-store"
],
"experiments": {
"typedRoutes": true
Expand Down
19 changes: 11 additions & 8 deletions apps/driver-app/app/+not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Link, Stack } from "expo-router";
import { StyleSheet, Text, View } from "react-native";
import { Link, Stack } from 'expo-router';
import { StyleSheet, Text, View } from 'react-native';

export default function NotFoundScreen() {
return (
<>
<Stack.Screen options={{ title: "Oops!" }} />
<Stack.Screen options={{ title: 'Oops!' }} />
<View style={styles.container}>
<Text>This screen doesn't exist.</Text>
<Link href="/map" style={styles.link}>
Expand All @@ -14,6 +14,9 @@ export default function NotFoundScreen() {
<Link href="/signup" style={styles.link}>
<Text>Go to SignUp!</Text>
</Link>
<Link href="/token-display" style={styles.link}>
<Text>Test tokens!</Text>
</Link>
</View>
</>
);
Expand All @@ -22,13 +25,13 @@ export default function NotFoundScreen() {
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
alignItems: 'center',
justifyContent: 'center',
padding: 20,
backgroundColor: "white",
backgroundColor: 'white'
},
link: {
marginTop: 15,
paddingVertical: 15,
},
paddingVertical: 15
}
});
27 changes: 13 additions & 14 deletions apps/driver-app/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import useBackgroundLocation from '@/src/hooks/use-background-location';
import useForegroundLocation from '@/src/hooks/use-foreground-location';
import Setup from '@/src/components/setup';
import store, { persistor } from '@/src/store';
import {
Inter_400Regular,
Inter_700Bold,
Expand All @@ -8,10 +8,10 @@ import {
import { Poppins_700Bold } from '@expo-google-fonts/poppins';
import Mapbox from '@rnmapbox/maps';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Stack } from 'expo-router';
import React from 'react';
import { View } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';

const queryClient = new QueryClient();

Expand All @@ -20,9 +20,6 @@ Mapbox.setAccessToken(
);

const App = () => {
useBackgroundLocation();
useForegroundLocation();

const [fontsLoaded] = useFonts({
Inter_400Regular,
Inter_700Bold,
Expand All @@ -34,13 +31,15 @@ const App = () => {
}

return (
<GestureHandlerRootView style={{ flex: 1 }}>
<QueryClientProvider client={queryClient}>
<Stack initialRouteName="signup">
<Stack.Screen name="signup" options={{ headerShown: false }} />
</Stack>
</QueryClientProvider>
</GestureHandlerRootView>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<GestureHandlerRootView style={{ flex: 1 }}>
<QueryClientProvider client={queryClient}>
<Setup />
</QueryClientProvider>
</GestureHandlerRootView>
</PersistGate>
</Provider>
);
};

Expand Down
70 changes: 45 additions & 25 deletions apps/driver-app/app/map.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import useUserLocation from "@/src/hooks/use-user-location";
import { Ionicons } from "@expo/vector-icons";
import Mapbox from "@rnmapbox/maps";
import React, { useEffect, useRef, useState } from "react";
import { Alert, StyleSheet, TouchableOpacity, View } from "react-native";
import useUserLocation from '@/src/hooks/use-user-location';
import { Ionicons, MaterialIcons } from '@expo/vector-icons';
import Mapbox from '@rnmapbox/maps';
import { useEffect, useRef, useState } from 'react';
import { Alert, StyleSheet, TouchableOpacity, View } from 'react-native';

export default function Map() {
const { location: userLocation, fetchUserLocation } = useUserLocation();
Expand All @@ -13,40 +13,60 @@ export default function Map() {
if (userLocation && !isMapInitialized) {
cameraRef.current?.setCamera({
centerCoordinate: userLocation,
zoomLevel: 16,
animationDuration: 2000,
zoomLevel: 14,
animationDuration: 2000
});
setIsMapInitialized(true);
}
}, [userLocation, isMapInitialized]);

const handleZoomToUserLocation = () => {
console.log("Zooming to user location...");
console.log('Zooming to user location...');
if (userLocation) {
cameraRef.current?.setCamera({
centerCoordinate: userLocation,
zoomLevel: 16,
animationDuration: 2000,
zoomLevel: 14,
animationDuration: 2000
});
} else {
Alert.alert("Location Error", "Unable to retrieve user location.");
Alert.alert('Location Error', 'Unable to retrieve user location.');
}
};

return (
<View style={{ flex: 1 }}>
<Mapbox.MapView style={{ flex: 1 }}>
<Mapbox.Camera ref={cameraRef} />
<Mapbox.UserLocation

<Mapbox.LocationPuck
visible
onUpdate={(location) => {
const coords: [number, number] = [
location.coords.longitude,
location.coords.latitude,
];
fetchUserLocation(); // Refresh location if desired
topImage="topImage"
puckBearingEnabled
pulsing={{
isEnabled: true,
color: '#CCCCCC',
radius: 50.0
}}
/>
<Mapbox.Images>
<Mapbox.Image name="topImage">
<View
style={{
borderColor: '#FFFFFF',
borderWidth: 2,
width: 40,
height: 40,
borderRadius: 20,
backgroundColor: '#FFFFFF',
justifyContent: 'center',
alignItems: 'center',

}}
>
<MaterialIcons name="navigation" size={30} color="#5588FF" />
</View>
</Mapbox.Image>
</Mapbox.Images>
</Mapbox.MapView>

<TouchableOpacity
Expand All @@ -61,14 +81,14 @@ export default function Map() {

const styles = StyleSheet.create({
button: {
position: "absolute",
bottom: 20,
right: 20,
position: 'absolute',
bottom: 30,
right: 10,
width: 50,
height: 50,
borderRadius: 25,
backgroundColor: "#007AFF",
alignItems: "center",
justifyContent: "center",
},
backgroundColor: '#007AFF',
alignItems: 'center',
justifyContent: 'center'
}
});
59 changes: 59 additions & 0 deletions apps/driver-app/app/token-display.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { RootState } from '@/src/store';
import AsyncStorage from '@react-native-async-storage/async-storage';
import React, { useEffect } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { useSelector } from 'react-redux';


const TokenDisplayScreen: React.FC = () => {
const accessToken = useSelector((state: RootState) => state.auth.accessToken);
const refreshToken = useSelector((state: RootState) => state.auth.refreshToken);

useEffect(() => {
AsyncStorage.setItem('test', 'testValue')
.then(() => AsyncStorage.getItem("persist:root"))
.then((value) => console.log('Persisted state:', value))
.catch((error) => console.error('AsyncStorage error:', error));
}, []);

return (
<View style={styles.container}>
<Text style={styles.title}>Token Information</Text>
<Text style={styles.label}>Access Token:</Text>
<Text style={styles.token}>
{accessToken || 'No access token available'}
</Text>
<Text style={styles.label}>Refresh Token:</Text>
<Text style={styles.token}>
{refreshToken || 'No refresh token available'}
</Text>
</View>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
backgroundColor: '#ffffff',
justifyContent: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 20,
textAlign: 'center',
},
label: {
fontSize: 18,
fontWeight: 'bold',
marginTop: 10,
},
token: {
fontSize: 16,
color: '#333',
marginTop: 5,
},
});

export default TokenDisplayScreen;
Loading

0 comments on commit 691aafa

Please sign in to comment.