diff --git a/.env.sample b/.env.sample index 4b711a3..8254d22 100644 --- a/.env.sample +++ b/.env.sample @@ -1,3 +1,3 @@ -HOST=https://gas-price-france-yaws.vercel.app/ +HOST=https://gas-price-france-yaws.vercel.app JAWG_API_KEY= MAPBOX_API_KEY= \ No newline at end of file diff --git a/README.md b/README.md index 6623cf7..8dab770 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,11 @@ In order to use Gas Prices, you must have Android studio on your computer ([Inst ```.env HOST=https://gas-price-france-yaws.vercel.app/ ``` + OR - You can use it on local, follow [GasPrices-Api](https://github.com/titi0267/GasPrices-Api) guidelines - + ```.env HOST=http://localhost:8080 ``` @@ -57,13 +58,13 @@ In order to use Gas Prices, you must have Android studio on your computer ([Inst MAPBOX_API_KEY= ``` -4. Install the dependencies with npm +3. Install the dependencies with npm ```bash npm i ``` -5. Extra steps for a physical device: +4. Extra steps for a physical device: - Connect your device to your computer - Make sure to enable developer mode - Make sure to enable file transfert @@ -75,13 +76,13 @@ Launch the project with ## Overview -Navigate on the map | Search for your itinerary & your fuel | Display the gas stations along your itinerary - --- | --- | --- -Image 1 | Image 2 | Image 1 +| Navigate on the map | Search for your itinerary & your fuel | Display the gas stations along your itinerary | +| --------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------- | +| Image 1 | Image 2 | Image 1 | -Click on one of the gas station | Check the lowest price | Redirect to maps and start driving ---- | --- | --- - Image 2 | Image 1 | Image 2 +| Click on one of the gas station | Check the lowest price | Redirect to maps and start driving | +| -------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------------- | +| Image 2 | Image 1 | Image 2 | ## About diff --git a/android/app/build.gradle b/android/app/build.gradle index f4e5eb2..f12c1ed 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,5 +1,6 @@ apply plugin: "com.android.application" apply plugin: "com.facebook.react" +apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" import com.android.build.OutputFile import groovy.json.JsonSlurper diff --git a/package-lock.json b/package-lock.json index 05c81a8..f1e5439 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gas-prices", - "version": "0.0.2", + "version": "0.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gas-prices", - "version": "0.0.2", + "version": "0.0.3", "dependencies": { "@react-native-async-storage/async-storage": "^1.21.0", "@react-native-community/geolocation": "^3.1.0", @@ -16,10 +16,12 @@ "@react-navigation/native-stack": "^6.9.17", "@rneui/themed": "^4.0.0-rc.7", "@rnmapbox/maps": "^10.0.5-rc.1", + "@turf/circle": "^6.5.0", "i": "^0.3.7", "npm": "^10.4.0", "react": "18.2.0", "react-native": "0.71.7", + "react-native-config": "^1.5.1", "react-native-dotenv": "^3.4.8", "react-native-elements": "^3.4.3", "react-native-linear-gradient": "^2.8.3", @@ -5114,6 +5116,18 @@ "url": "https://opencollective.com/turf" } }, + "node_modules/@turf/circle": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/circle/-/circle-6.5.0.tgz", + "integrity": "sha512-oU1+Kq9DgRnoSbWFHKnnUdTmtcRUMmHoV9DjTXu9vOLNV5OWtAAh1VZ+mzsioGGzoDNT/V5igbFOkMfBQc0B6A==", + "dependencies": { + "@turf/destination": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, "node_modules/@turf/destination": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-6.5.0.tgz", @@ -16646,6 +16660,19 @@ "nullthrows": "^1.1.1" } }, + "node_modules/react-native-config": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/react-native-config/-/react-native-config-1.5.1.tgz", + "integrity": "sha512-g1xNgt1tV95FCX+iWz6YJonxXkQX0GdD3fB8xQtR1GUBEqweB9zMROW77gi2TygmYmUkBI7LU4pES+zcTyK4HA==", + "peerDependencies": { + "react-native-windows": ">=0.61" + }, + "peerDependenciesMeta": { + "react-native-windows": { + "optional": true + } + } + }, "node_modules/react-native-dotenv": { "version": "3.4.8", "resolved": "https://registry.npmjs.org/react-native-dotenv/-/react-native-dotenv-3.4.8.tgz", diff --git a/package.json b/package.json index 9b8a1cb..0366a52 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,12 @@ "@react-navigation/native-stack": "^6.9.17", "@rneui/themed": "^4.0.0-rc.7", "@rnmapbox/maps": "^10.0.5-rc.1", + "@turf/circle": "^6.5.0", "i": "^0.3.7", "npm": "^10.4.0", "react": "18.2.0", "react-native": "0.71.7", + "react-native-config": "^1.5.1", "react-native-dotenv": "^3.4.8", "react-native-elements": "^3.4.3", "react-native-linear-gradient": "^2.8.3", diff --git a/src/Components/Input/index.tsx b/src/Components/Input/index.tsx index 1bcacbb..4cbaa8c 100644 --- a/src/Components/Input/index.tsx +++ b/src/Components/Input/index.tsx @@ -11,8 +11,10 @@ const CustomInput = (props: { onChangeText: (value: string) => void; placeholder: string; inputValue?: string; + setIsFocused: (value: boolean) => void; }) => { - const {onClearInput, onChangeText, placeholder, inputValue} = props; + const {onClearInput, onChangeText, placeholder, inputValue, setIsFocused} = + props; return ( { + setIsFocused(false); + }} + onFocus={() => { + setIsFocused(true); + }} style={[inputStyles.placeholder, inputStyles.input]} placeholder={placeholder} value={inputValue} diff --git a/src/Pages/Map/index.tsx b/src/Pages/Map/index.tsx index d8e3b20..32f4008 100644 --- a/src/Pages/Map/index.tsx +++ b/src/Pages/Map/index.tsx @@ -6,7 +6,6 @@ import { TouchableOpacity, View, } from 'react-native'; -import {JAWG_API_KEY, MAPBOX_API_KEY} from '@env'; import {useEffect, useRef, useState} from 'react'; import {useIsFocused, useRoute} from '@react-navigation/native'; import getLocation from '../../services/getCurrentLocation'; @@ -18,8 +17,10 @@ import departmentCodeService from '../../services/departmentCode.service'; import gasStationsService from '../../services/gasStations.service'; import asyncStorageService from '../../services/asyncStorage.service'; import CustomCard from '../../Components/Card'; +import circle from '@turf/circle'; +import Config from 'react-native-config'; -Mapbox.setAccessToken(MAPBOX_API_KEY); +Mapbox.setAccessToken(Config.MAPBOX_API_KEY as string); const Map = () => { const route = useRoute(); @@ -39,6 +40,7 @@ const Map = () => { const [refinedStations, setRefinedStations] = useState( null, ); + const [radius, setRadius] = useState(Math.pow(2, zoomLevel) * 100); const [isLoading, setIsLoading] = useState(true); const [departmentCodes, setDepartmentCodes] = useState([]); const [gasType, setGasType] = useState(); @@ -76,6 +78,7 @@ const Map = () => { end: end, }, setGeoJson, + setIsLoading, ); } else { setIsLoading(false); @@ -152,6 +155,14 @@ const Map = () => { } }, [gasStations, gasType]); + const layerStyles = { + route: { + lineColor: '#00F5FD', + lineWidth: 1.5, + lineDasharray: [1, 0], + }, + }; + useEffect(() => { if (refinedStations) { asyncStorageService.storeData('gasPumps', refinedStations as any[]); @@ -162,6 +173,15 @@ const Map = () => { } }, [refinedStations, end, start]); + const handleRegionDidChange = async () => { + if (mapRef.current) { + const zoomLevel = await mapRef.current.getZoom(); + console.log('Zoom level:', zoomLevel); + const radius = Math.pow(2, -zoomLevel) * 1000; + setRadius(radius > 0.5 ? 0.5 : radius); + } + }; + return ( { ref={mapRef} logoEnabled={false} attributionEnabled={false} - styleURL={`https://tile.jawg.io/jawg-streets.json?access-token=${JAWG_API_KEY}`}> + onRegionIsChanging={handleRegionDidChange} + styleURL={`https://tile.jawg.io/jawg-streets.json?access-token=${Config.JAWG_API_KEY}`}> {isLoading == false || (!end && !start) ? ( { ) : ( <> )} + {location ? ( + + + + + ) : ( + <> + )} {geoJson && ( { const navigation = useNavigation>(); - const [cityNamesStart, setCityNamesStart] = useState([]); + const [cityNamesStart, setCityNamesStart] = useState([ + 'Votre position', + ]); const [cityCoordsStart, setCityCoordsStart] = useState( null, ); const [inputValueStart, setInputValueStart] = useState(''); - const [cityNamesEnd, setCityNamesEnd] = useState([]); + const [cityNamesEnd, setCityNamesEnd] = useState([ + 'Votre position', + ]); const [cityCoordsEnd, setCityCoordsEnd] = useState(null); const [inputValueEnd, setInputValueEnd] = useState(''); @@ -43,11 +47,20 @@ const Search = () => { const [isFetchStart, setIsFetchStart] = useState(false); const [isFetchEnd, setIsFetchEnd] = useState(false); const [selectedFuel, setSelectedFuel] = useState(null); + const [startIsFocused, setStartIsFocused] = useState(false); + const [endIsFocused, setEndIsFocused] = useState(false); const setLocationCallback = (value: LocationType | string) => { setLocation(value as string); }; + const setStartIsFocusedCallback = (value: boolean) => { + setStartIsFocused(value); + }; + const setEndIsFocusedCallback = (value: boolean) => { + setEndIsFocused(value); + }; + useEffect(() => { getLocation(setLocationCallback, 'string'); }, []); @@ -102,6 +115,9 @@ const Search = () => { setIsFetchStartCallback, ); } + if (inputValueStart.length == 0) { + setIsFetchStart(false); + } }, 400); return () => clearTimeout(debounceTimer); }, [inputValueStart, isStartCitySelected]); @@ -118,6 +134,9 @@ const Search = () => { cityNamesEndCallback, setIsFetchEndCallback, ); + if (inputValueEnd.length == 0) { + setIsFetchEnd(false); + } }, 400); return () => clearTimeout(debounceTimer); }, [inputValueEnd, isEndCitySelected]); @@ -131,7 +150,14 @@ const Search = () => { setIsStartCitySelected(true); setInputValueStart(item); setCityNamesStart([]); - fetchCityPosition({adress: item}, setCityCoordsStartCallback); + if (item == 'Votre position') { + let values: string[] = location.split(','); + + let invertedValues: string = values.reverse().join(','); + setCityCoordsStart({label: 'Votre position', geometry: invertedValues}); + } else { + fetchCityPosition({adress: item}, setCityCoordsStartCallback); + } }; const onPressItemEnd = (item: string) => { @@ -139,20 +165,30 @@ const Search = () => { setIsEndCitySelected(true); setInputValueEnd(item); setCityNamesEnd([]); - fetchCityPosition({adress: item}, setCityCoordsEndCallback); + if (item == 'Votre position') { + let values: string[] = location.split(','); + + let invertedValues: string = values.reverse().join(','); + setCityCoordsEnd({label: 'Votre position', geometry: invertedValues}); + } else { + fetchCityPosition({adress: item}, setCityCoordsEndCallback); + } }; const onClearInputStart = () => { setIsStartCitySelected(false); setInputValueStart(''); - setCityNamesStart([]); + Keyboard.dismiss(); + setStartIsFocused(false); + setCityNamesStart(['Votre position']); setCityCoordsStart(null); }; const onClearInputEnd = () => { setIsEndCitySelected(false); + Keyboard.dismiss(); setInputValueEnd(''); - setCityNamesEnd([]); + setCityNamesEnd(['Votre position']); setCityCoordsEnd(null); }; @@ -268,6 +304,7 @@ const Search = () => { onChangeText={onChangeTextStart} onClearInput={onClearInputStart} placeholder="Depart" + setIsFocused={setStartIsFocusedCallback} inputValue={inputValueStart}> { onChangeText={onChangeTextEnd} onClearInput={onClearInputEnd} placeholder="Arrivee" + setIsFocused={setEndIsFocusedCallback} inputValue={inputValueEnd}> - {cityNamesStart.length != 0 ? ( + {cityNamesStart.length != 0 && startIsFocused ? ( { ) : ( <> )} - {cityNamesEnd.length != 0 ? ( + {cityNamesEnd.length != 0 && endIsFocused ? ( void, ) => { setLoading(true); - const res = await fetch(`${HOST}/cityName`, { + const res = await fetch(`${Config.HOST}/cityName`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -31,7 +31,7 @@ const fetchCityPosition = async ( body: {adress: string}, setPosition: (value: {label: string; geometry: string}) => void, ) => { - const res = await fetch(`${HOST}/cityData`, { + const res = await fetch(`${Config.HOST}/cityData`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/services/departmentCode.service.ts b/src/services/departmentCode.service.ts index 809c5d4..e7420b5 100644 --- a/src/services/departmentCode.service.ts +++ b/src/services/departmentCode.service.ts @@ -1,9 +1,9 @@ -import {HOST} from '@env'; +import Config from 'react-native-config'; const fetchDepartmentCode = async (body: { coords: number[]; }): Promise => { - const res = await fetch(`${HOST}/geoCode`, { + const res = await fetch(`${Config.HOST}/geoCode`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/services/gasStations.service.ts b/src/services/gasStations.service.ts index 27cc71b..fe5bdde 100644 --- a/src/services/gasStations.service.ts +++ b/src/services/gasStations.service.ts @@ -1,7 +1,7 @@ -import {HOST} from '@env'; +import Config from 'react-native-config'; const fetchGasStationList = async (body: {code_department: string}) => { - const res = await fetch(`${HOST}/gasStations`, { + const res = await fetch(`${Config.HOST}/gasStations`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/services/geoJson.service.ts b/src/services/geoJson.service.ts index 654d84a..17c212c 100644 --- a/src/services/geoJson.service.ts +++ b/src/services/geoJson.service.ts @@ -1,4 +1,4 @@ -import {HOST} from '@env'; +import Config from 'react-native-config'; const fetchGeoJsonResults = async ( body: { @@ -6,17 +6,23 @@ const fetchGeoJsonResults = async ( end: string; }, setData: (value: any) => void, + setIsLoading: (value: boolean) => void, ) => { - const res = await fetch(`${HOST}/geoJson`, { + console.log(body); + const res = await fetch(`${Config.HOST}/geoJson`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(body), }); - if (!res.ok) throw Error('Error on osrm services'); - const resolve = await res.json(); - setData(resolve[0]); + try { + if (!res.ok) throw Error('Error on osrm services'); + const resolve = await res.json(); + setData(resolve[0]); + } catch (e) { + setIsLoading(false); + } }; export default fetchGeoJsonResults; diff --git a/src/services/updateApp.service.ts b/src/services/updateApp.service.ts index fafd493..672c938 100644 --- a/src/services/updateApp.service.ts +++ b/src/services/updateApp.service.ts @@ -1,16 +1,16 @@ -import {HOST} from '@env'; +import Config from 'react-native-config'; const updateApp = async (setVersion: (value: any) => void) => { const pj = require('../../package.json'); const body = {version: pj.version}; - const res = await fetch(`${HOST}/update`, { + const res = await fetch(`${Config.HOST}/update`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(body), }); - if (!res.ok) throw Error('Error on osrm services'); + if (!res.ok) throw Error('Error on version check'); const resolve = await res.json(); setVersion(resolve); };