diff --git a/App.js b/App.js
index 352ecbe..4e1a2fe 100644
--- a/App.js
+++ b/App.js
@@ -3,7 +3,7 @@ import React from 'react';
import { Provider as PaperProvider } from 'react-native-paper';
import { RecoilRoot } from 'recoil';
-import Main from './Main';
+import Main from './src/Main';
const App = () => {
React.useEffect(() => {
diff --git a/app.config.js b/app.config.js
index e3812f6..b31e3df 100644
--- a/app.config.js
+++ b/app.config.js
@@ -2,7 +2,7 @@ module.exports = {
expo: {
name: 'Ames Ride',
slug: 'AmesRide',
- version: '1.0.4',
+ version: '1.0.5',
orientation: 'portrait',
icon: './assets/icon.png',
userInterfaceStyle: 'light',
@@ -20,7 +20,7 @@ module.exports = {
supportsTablet: true,
},
android: {
- versionCode: 6,
+ versionCode: 7,
config: {
googleMaps: {
apiKey: process.env.GOOGLE_MAPS_API_KEY,
diff --git a/components/Map/components/Stops.js b/components/Map/components/Stops.js
deleted file mode 100644
index 74d7483..0000000
--- a/components/Map/components/Stops.js
+++ /dev/null
@@ -1,88 +0,0 @@
-import React from 'react';
-import Svg, { Path } from 'react-native-svg';
-import { useRecoilValue } from 'recoil';
-
-import { favoriteStopsState } from '../../../state/atoms';
-import { currentRouteStopDetailsState, favoriteStopDetailsState } from '../../../state/selectors';
-import ImagePin from './ImagePin';
-
-const Stops = ({ onPress }) => {
- const favoriteStopIDs = useRecoilValue(favoriteStopsState);
- const favoriteStopDetails = useRecoilValue(favoriteStopDetailsState);
- const stops = useRecoilValue(currentRouteStopDetailsState);
-
- if (stops && stops.length > 0) {
- return stops.map((s, i) => {
- const isFavorite = favoriteStopIDs.has(s.RtpiNumber);
- // the seprate rendering of ImagePin is required to ensure a rerender of the element
- // otherwise performance will be 0 fps
- if (isFavorite)
- return (
-
-
-
- );
- return (
-
-
-
- );
- });
- }
-
- if (favoriteStopDetails && favoriteStopDetails.length > 0) {
- return favoriteStopDetails.map((s, i) => (
-
-
-
- ));
- }
-
- return null;
-};
-
-const StopImage = (props) => (
-
-);
-
-const FavoriteImage = (props) => (
-
-);
-
-export default Stops;
diff --git a/components/StopInfo/components/StopDetailsView.js b/components/StopInfo/components/StopDetailsView.js
deleted file mode 100644
index 16fedfc..0000000
--- a/components/StopInfo/components/StopDetailsView.js
+++ /dev/null
@@ -1,204 +0,0 @@
-import { Text } from '@ui-kitten/components';
-import React from 'react';
-import { AppState, ScrollView, View } from 'react-native';
-import { Button, Menu } from 'react-native-paper';
-import GestureRecognizer from 'react-native-swipe-gestures';
-import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
-import Pressable from 'react-native/Libraries/Components/Pressable/Pressable';
-import { useRecoilValue } from 'recoil';
-
-import {
- currentStopState,
- dispatcherState,
- favoriteRoutesState,
- loadingArrivalsState,
- routePatternsState,
- routesState,
- userSettingsState,
-} from '../../../state/atoms';
-import { isCurrentStopFavorite, upcomingArrivalsSorted } from '../../../state/selectors';
-import LoadingIndicator from '../../ArrivalsLoadingIndicator';
-
-const StopDetailsView = () => {
- const stop = useRecoilValue(currentStopState);
- const upcomingArrivals = useRecoilValue(upcomingArrivalsSorted);
- const routes = useRecoilValue(routesState);
- const routePatterns = useRecoilValue(routePatternsState);
- const dispatcher = useRecoilValue(dispatcherState);
- const settings = useRecoilValue(userSettingsState);
- const favoriteRouteIDs = useRecoilValue(favoriteRoutesState);
- const currentStopFavorite = useRecoilValue(isCurrentStopFavorite);
- const isLoadingArrivals = useRecoilValue(loadingArrivalsState);
- const [menuOpen, setMenuOpen] = React.useState(false);
-
- // used so text doesn't disappear when sliding out
- const [cached, setCached] = React.useState({ stopName: '', arrivals: [] });
-
- const storeStopNameInCache = () => {
- setCached((c) => ({ ...c, stopName: stop.Name }));
- };
-
- const toggleFilterSetting = () => {
- dispatcher?.toggleUserSetting('showFavoriteArrivalsOnly');
- };
-
- const openMenu = () => {
- setMenuOpen(true);
- };
- const closeMenu = () => {
- setMenuOpen(false);
- };
-
- const handleUpdatedStop = () => {
- if (!stop) return;
-
- storeStopNameInCache();
- // fetch bus and upcoming arrivals when app comes back into foreground
- const subscription = AppState.addEventListener('change', (nextAppState) => {
- if (nextAppState === 'active') {
- dispatcher?.fetchUpcomingArrivals(stop);
- }
- });
-
- const interval = fetchUpcomingArrivalsOnInterval(stop, dispatcher);
- return () => {
- clearInterval(interval);
- subscription.remove();
- };
- };
-
- const storeArrivalsInCache = () => {
- if (!upcomingArrivals) return;
-
- setCached((c) => ({ ...c, arrivals: upcomingArrivals }));
- };
-
- const favoriteStop = () => {
- dispatcher?.toggleFavoriteStop(stop.RtpiNumber);
- };
-
- React.useEffect(handleUpdatedStop, [stop]);
- React.useEffect(storeArrivalsInCache, [upcomingArrivals]);
-
- return (
-
-
-
-
- {stop?.Name || cached.stopName}
-
-
-
-
-
-
-
-
- Upcoming Arrivals{settings.showFavoriteArrivalsOnly && ' (favorite routes)'}
-
- {upcomingArrivals === null ? null : upcomingArrivals.length === 0 ? (
- No upcoming arrivals.
- ) : (
- upcomingArrivals.map((arrival) => {
- const r = routes[routePatterns[arrival.RouteID]];
-
- if (settings?.showFavoriteArrivalsOnly && !favoriteRouteIDs.has(r.ID)) return null;
-
- const arrivalDate = new Date(new Date().getTime());
- const minutes = Number.parseInt(
- arrival.ArriveTime.substring(
- arrival.ArriveTime.indexOf(':') + 1,
- arrival.ArriveTime.indexOf(':') + 3
- )
- );
- arrivalDate.setMinutes(minutes);
- let hours = Number.parseInt(
- arrival.ArriveTime.substring(0, arrival.ArriveTime.indexOf(':'))
- );
- if (hours == 12 && arrival.ArriveTime.endsWith('AM')) hours = 0;
- arrivalDate.setHours(hours);
-
- if (new Date().getHours() >= 12 && arrival.ArriveTime.endsWith('AM')) {
- arrivalDate.setTime(arrivalDate.getTime() + 24 * 60 * 60 * 1000);
- } else if (arrival.ArriveTime.endsWith('PM')) {
- arrivalDate.setTime(arrivalDate.getTime() + 12 * 60 * 60 * 1000);
- }
-
- const diffMins = Math.round((arrivalDate - new Date()) / 1000 / 60);
-
- if (diffMins > 180) return null;
-
- return (
- {
- dispatcher?.updateCurrentRoute(r.ID, false);
- }}>
-
-
- {r.DisplayName} - {arrival.ArriveTime} (
- {diffMins > 1
- ? diffMins + ' minutes'
- : diffMins === 1
- ? '1 minute'
- : 'arriving'}
- )
-
-
-
- );
- })
- )}
-
-
-
- );
-};
-
-const fetchUpcomingArrivalsOnInterval = (stop, dispatcher) => {
- const fetchUpcomingArrivals = () => dispatcher?.fetchUpcomingArrivals(stop);
- fetchUpcomingArrivals();
- return setInterval(fetchUpcomingArrivals, 15 * 1000);
-};
-
-export default StopDetailsView;
diff --git a/package.json b/package.json
index aae3516..663ce78 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ames-ride",
- "version": "1.0.4",
+ "version": "1.0.5",
"main": "node_modules/expo/AppEntry.js",
"license": "GNU3",
"scripts": {
diff --git a/Main.js b/src/Main.js
similarity index 100%
rename from Main.js
rename to src/Main.js
diff --git a/components/ArrivalsLoadingIndicator.js b/src/components/LoadingIndicator.js
similarity index 100%
rename from components/ArrivalsLoadingIndicator.js
rename to src/components/LoadingIndicator.js
diff --git a/components/Map/Map.js b/src/components/Map/Map.js
similarity index 75%
rename from components/Map/Map.js
rename to src/components/Map/Map.js
index c9afa6b..7e224ed 100644
--- a/components/Map/Map.js
+++ b/src/components/Map/Map.js
@@ -14,6 +14,13 @@ const { width, height } = Dimensions.get('window');
const FETCH_BUS_SECONDS_INTERVAL = 5000;
+const isuCampusRegion = {
+ latitude: 42.02663,
+ longitude: -93.6466,
+ latitudeDelta: 0.01,
+ longitudeDelta: 0.01,
+};
+
const Map = () => {
const route = useRecoilValue(currentRoute);
const location = useRecoilValue(userLocationState);
@@ -21,32 +28,32 @@ const Map = () => {
const mapRef = useRef(null);
- React.useEffect(() => {
- const interval = fetchVehiclesOnInterval(route, dispatcher);
+ const updateVehiclesOnForeground = () => {
+ const fetchVehiclesInterval = fetchVehiclesOnInterval(route, dispatcher);
// fetch bus arrivals when app comes back into foreground
- const subscription = AppState.addEventListener('change', (nextAppState) => {
+ const appToForegroundSubscription = AppState.addEventListener('change', (nextAppState) => {
if (nextAppState === 'active') {
fetchVehicle(route, dispatcher);
}
});
return () => {
- clearInterval(interval);
- subscription.remove();
+ clearInterval(fetchVehiclesInterval);
+ appToForegroundSubscription.remove();
};
- }, [route]);
+ };
- React.useEffect(() => {
+ const getCurrentUserLocation = () => {
(async () => {
const location = await Location.getCurrentPositionAsync();
dispatcher?.setUserLocation(location);
})().catch(() => {
console.log('No location permissions granted.');
});
- }, []);
+ };
- React.useEffect(() => {
+ const moveMapToUser = () => {
if (!location) return;
mapRef.current.animateToRegion({
@@ -55,22 +62,17 @@ const Map = () => {
longitudeDelta: 0.01,
latitudeDelta: 0.01,
});
- }, [location]);
-
- const setStop = (stop) => {
- dispatcher?.setCurrentStop(stop);
};
+ React.useEffect(updateVehiclesOnForeground, [route]);
+ React.useEffect(getCurrentUserLocation, []);
+ React.useEffect(moveMapToUser, [location]);
+
return (
{
showsIndoors={false}
showsTraffic={false}>
-
+
diff --git a/components/Map/components/ImagePin.js b/src/components/Map/components/ImagePin.js
similarity index 100%
rename from components/Map/components/ImagePin.js
rename to src/components/Map/components/ImagePin.js
diff --git a/components/Map/components/RouteLine.js b/src/components/Map/components/RouteLine.js
similarity index 69%
rename from components/Map/components/RouteLine.js
rename to src/components/Map/components/RouteLine.js
index 4129262..992e276 100644
--- a/components/Map/components/RouteLine.js
+++ b/src/components/Map/components/RouteLine.js
@@ -1,14 +1,17 @@
+import React from 'react';
import { Polyline } from 'react-native-maps';
import { useRecoilValue } from 'recoil';
import { routesState } from '../../../state/atoms';
+import { ALL_ROUTES, FAVORITE_ROUTES } from '../../../state/constants';
const RouteLine = ({ route }) => {
let stops = [];
const routes = useRecoilValue(routesState);
- if (route.ID === -2) return null;
- if (route.ID === -1) {
+ if (route.ID === FAVORITE_ROUTES) return null;
+
+ if (route.ID === ALL_ROUTES) {
stops = Object.values(routes);
} else {
stops = [route];
@@ -24,4 +27,4 @@ const RouteLine = ({ route }) => {
));
};
-export default RouteLine;
+export default React.memo(RouteLine);
diff --git a/src/components/Map/components/Stops.js b/src/components/Map/components/Stops.js
new file mode 100644
index 0000000..2a3420b
--- /dev/null
+++ b/src/components/Map/components/Stops.js
@@ -0,0 +1,60 @@
+import React from 'react';
+import { useRecoilValue } from 'recoil';
+
+import { dispatcherState, favoriteStopsState } from '../../../state/atoms';
+import { currentRouteStopDetailsState, favoriteStopDetailsState } from '../../../state/selectors';
+import FavoriteStopImage from '../../icons/FavoriteStopImage';
+import RegularStopImage from '../../icons/RegularStopImage';
+import ImagePin from './ImagePin';
+
+const Stops = () => {
+ const favoriteStopIDs = useRecoilValue(favoriteStopsState);
+ const favoriteStopDetails = useRecoilValue(favoriteStopDetailsState);
+ const stops = useRecoilValue(currentRouteStopDetailsState);
+ const dispatcher = useRecoilValue(dispatcherState);
+
+ const setActiveStop = (stop) => {
+ dispatcher.setCurrentStop(stop);
+ };
+
+ if (stops && stops.length > 0) {
+ return stops.map((s, i) => {
+ const isFavorite = favoriteStopIDs.has(s.RtpiNumber);
+ // the seprate rendering of ImagePin is required to ensure a rerender of the element
+ // otherwise performance will be 0 fps
+ if (isFavorite)
+ return (
+
+
+
+ );
+ return (
+
+
+
+ );
+ });
+ }
+
+ if (favoriteStopDetails && favoriteStopDetails.length > 0) {
+ return favoriteStopDetails.map((s, i) => (
+
+
+
+ ));
+ }
+
+ return null;
+};
+
+export default React.memo(Stops);
diff --git a/components/Map/components/VehicleView.js b/src/components/Map/components/VehicleView.js
similarity index 90%
rename from components/Map/components/VehicleView.js
rename to src/components/Map/components/VehicleView.js
index f6efda9..6b68796 100644
--- a/components/Map/components/VehicleView.js
+++ b/src/components/Map/components/VehicleView.js
@@ -1,10 +1,12 @@
+import React from 'react';
import { Image, View } from 'react-native';
import { Marker } from 'react-native-maps';
-import busImage from '../../../assets/arrow.png';
+import busImage from '../../../../assets/arrow.png';
const VehicleView = ({ details }) => {
const transformMarkerDegrees = calculateMarkerRotation(details);
+
return (
{
return 0;
};
-export default VehicleView;
+export default React.memo(VehicleView);
diff --git a/components/Map/components/Vehicles.js b/src/components/Map/components/Vehicles.js
similarity index 65%
rename from components/Map/components/Vehicles.js
rename to src/components/Map/components/Vehicles.js
index 68ba293..fd0ff5c 100644
--- a/components/Map/components/Vehicles.js
+++ b/src/components/Map/components/Vehicles.js
@@ -2,21 +2,22 @@ import React from 'react';
import { ToastAndroid } from 'react-native';
import { useRecoilValue } from 'recoil';
-import { currentRouteRowState, vehicleLocationState } from '../../../state/atoms';
+import { vehicleLocationState } from '../../../state/atoms';
+import { isIndividualRoute } from '../../../state/selectors';
import VehicleView from './VehicleView';
const Vehicles = () => {
const vehicles = useRecoilValue(vehicleLocationState);
- const currentRouteID = useRecoilValue(currentRouteRowState);
+ const isRegularRoute = useRecoilValue(isIndividualRoute);
React.useEffect(() => {
if (vehicles === null || vehicles.length > 0) return;
ToastAndroid.show('No busses found on route.', ToastAndroid.SHORT);
}, [vehicles]);
- if (currentRouteID < 0) return null;
+ if (!isRegularRoute) return null;
return vehicles?.map((v) => );
};
-export default Vehicles;
+export default React.memo(Vehicles);
diff --git a/components/RouteSelect.js b/src/components/RouteSelect.js
similarity index 93%
rename from components/RouteSelect.js
rename to src/components/RouteSelect.js
index 27fcba8..d7d2ce3 100644
--- a/components/RouteSelect.js
+++ b/src/components/RouteSelect.js
@@ -12,8 +12,9 @@ import {
favoriteRoutesState,
loadingVehiclesState,
} from '../state/atoms';
+import { ALL_ROUTES, FAVORITE_ROUTES } from '../state/constants';
import { favoriteRoutesOnlyState, routesSortedState } from '../state/selectors';
-import LoadingIndicator from './ArrivalsLoadingIndicator';
+import LoadingIndicator from './LoadingIndicator';
const setup = async () => {
await localForage.defineDriver(fsDriver);
@@ -43,8 +44,8 @@ const RouteSelect = () => {
React.useEffect(() => {
setRouteList([
- { label: 'Favorite Stops', value: -2, key: 'fav', index: 0 },
- { label: 'All Stops/Routes', value: -1, key: 'all', index: 1 },
+ { label: 'Favorite Stops', value: FAVORITE_ROUTES, key: 'fav', index: 0 },
+ { label: 'All Stops/Routes', value: ALL_ROUTES, key: 'all', index: 1 },
...favoriteRoutes.map((r, i) => ({
label: r.DisplayName,
value: r.ID,
@@ -70,7 +71,7 @@ const RouteSelect = () => {
const handleSelect = (index) => {
setShowDropDown(false);
- if (index == currentRoute) {
+ if (index === currentRoute) {
return;
}
if (index === 0) dispatcher?.updateCurrentRoute(null);
diff --git a/components/StopInfo/StopInfo.js b/src/components/StopInfo/StopInfo.js
similarity index 100%
rename from components/StopInfo/StopInfo.js
rename to src/components/StopInfo/StopInfo.js
diff --git a/components/StopInfo/components/BottomSheet.js b/src/components/StopInfo/components/BottomSheet.js
similarity index 100%
rename from components/StopInfo/components/BottomSheet.js
rename to src/components/StopInfo/components/BottomSheet.js
diff --git a/src/components/StopInfo/components/StopDetailsView.js b/src/components/StopInfo/components/StopDetailsView.js
new file mode 100644
index 0000000..26d401a
--- /dev/null
+++ b/src/components/StopInfo/components/StopDetailsView.js
@@ -0,0 +1,53 @@
+import React from 'react';
+import { AppState, ScrollView, View } from 'react-native';
+import { useRecoilValue } from 'recoil';
+
+import { currentStopState, dispatcherState, loadingArrivalsState } from '../../../state/atoms';
+import LoadingIndicator from '../../LoadingIndicator';
+import TopBanner from './TopBanner';
+import UpcomingArrivals from './UpcomingArrivals';
+
+const StopDetailsView = () => {
+ const stop = useRecoilValue(currentStopState);
+ const dispatcher = useRecoilValue(dispatcherState);
+ const isLoadingArrivals = useRecoilValue(loadingArrivalsState);
+
+ const handleUpdatedStop = () => {
+ if (!stop) return;
+
+ // fetch upcoming arrivals when app comes back into foreground
+ const subscription = AppState.addEventListener('change', (nextAppState) => {
+ if (nextAppState === 'active') {
+ dispatcher?.fetchUpcomingArrivals(stop);
+ }
+ });
+
+ const interval = fetchUpcomingArrivalsOnInterval(stop, dispatcher);
+ return () => {
+ clearInterval(interval);
+ subscription.remove();
+ };
+ };
+
+ React.useEffect(handleUpdatedStop, [stop]);
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+const fetchUpcomingArrivalsOnInterval = (stop, dispatcher) => {
+ const fetchUpcomingArrivals = () => dispatcher?.fetchUpcomingArrivals(stop);
+ fetchUpcomingArrivals();
+ return setInterval(fetchUpcomingArrivals, 15 * 1000);
+};
+
+export default StopDetailsView;
diff --git a/src/components/StopInfo/components/TopBanner.js b/src/components/StopInfo/components/TopBanner.js
new file mode 100644
index 0000000..7bb0433
--- /dev/null
+++ b/src/components/StopInfo/components/TopBanner.js
@@ -0,0 +1,94 @@
+import { Text } from '@ui-kitten/components';
+import React from 'react';
+import { View } from 'react-native';
+import { Button, Menu } from 'react-native-paper';
+import GestureRecognizer from 'react-native-swipe-gestures';
+import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
+import { useRecoilValue } from 'recoil';
+
+import { dispatcherState, currentStopState, userSettingsState } from '../../../state/atoms';
+import { isCurrentStopFavorite } from '../../../state/selectors';
+
+const TopBanner = ({ stop }) => {
+ const dispatcher = useRecoilValue(dispatcherState);
+ const currentStopFavorite = useRecoilValue(isCurrentStopFavorite);
+ const settings = useRecoilValue(userSettingsState);
+
+ const [menuOpen, setMenuOpen] = React.useState(false);
+
+ // used so text doesn't disappear when sliding out
+ const [cached, setCached] = React.useState('');
+
+ const toggleFilterSetting = () => {
+ dispatcher?.toggleUserSetting('showFavoriteArrivalsOnly');
+ };
+
+ const openMenu = () => {
+ setMenuOpen(true);
+ };
+ const closeMenu = () => {
+ setMenuOpen(false);
+ };
+
+ const favoriteStop = () => {
+ dispatcher?.toggleFavoriteStop(stop.RtpiNumber);
+ };
+
+ const storeStopNameInCache = () => {
+ setCached(stop.Name);
+ };
+
+ const handleUpdatedStop = () => {
+ if (!stop) return;
+
+ storeStopNameInCache();
+ };
+
+ React.useEffect(handleUpdatedStop, [stop]);
+
+ return (
+
+
+
+ {stop?.Name || cached}
+
+
+
+
+ );
+};
+
+export default React.memo(TopBanner);
diff --git a/src/components/StopInfo/components/UpcomingArrivals.js b/src/components/StopInfo/components/UpcomingArrivals.js
new file mode 100644
index 0000000..9c36bf9
--- /dev/null
+++ b/src/components/StopInfo/components/UpcomingArrivals.js
@@ -0,0 +1,106 @@
+import { Text } from '@ui-kitten/components';
+import React from 'react';
+import { View } from 'react-native';
+import Pressable from 'react-native/Libraries/Components/Pressable/Pressable';
+import { useRecoilValue } from 'recoil';
+
+import {
+ dispatcherState,
+ favoriteRoutesState,
+ routePatternsState,
+ routesState,
+ userSettingsState,
+} from '../../../state/atoms';
+import { upcomingArrivalsSorted } from '../../../state/selectors';
+
+const UpcomingArrivals = () => {
+ const upcomingArrivals = useRecoilValue(upcomingArrivalsSorted);
+ const routes = useRecoilValue(routesState);
+ const routePatterns = useRecoilValue(routePatternsState);
+ const dispatcher = useRecoilValue(dispatcherState);
+ const settings = useRecoilValue(userSettingsState);
+ const favoriteRouteIDs = useRecoilValue(favoriteRoutesState);
+
+ let renderArrivals = [];
+ if (upcomingArrivals) {
+ renderArrivals = upcomingArrivals.map((arrival) => {
+ const r = routes[routePatterns[arrival.RouteID]];
+
+ if (settings?.showFavoriteArrivalsOnly && !favoriteRouteIDs.has(r.ID)) return null;
+
+ const diffMins = getMinsToTime(arrival.ArriveTime);
+
+ if (diffMins > 180) return null;
+
+ return (
+ {
+ dispatcher?.updateCurrentRoute(r.ID, false);
+ }}>
+
+
+ {r.DisplayName} - {arrival.ArriveTime} (
+ {diffMins > 1 ? diffMins + ' minutes' : diffMins === 1 ? '1 minute' : 'arriving'})
+
+
+
+ );
+ });
+ }
+ return (
+ <>
+
+ Upcoming Arrivals{settings.showFavoriteArrivalsOnly && ' (favorite routes)'}
+
+
+ {upcomingArrivals === null ? null : upcomingArrivals.length === 0 ? (
+ No upcoming arrivals.
+ ) : renderArrivals.filter((arrival) => arrival !== null).length > 0 ? (
+ renderArrivals
+ ) : (
+ No upcoming arrivals on favorited routes.
+ )}
+ >
+ );
+};
+
+/**
+ * Get the minutes until a given time. Assumes the time is not in the past.
+ * @param {*} arriveTime arrival time in format HH:MM [AM|PM]
+ * @returns minutes until the given time
+ */
+const getMinsToTime = (arriveTime) => {
+ const arrivalDate = new Date(new Date().getTime());
+ const minutes = Number.parseInt(
+ arriveTime.substring(arriveTime.indexOf(':') + 1, arriveTime.indexOf(':') + 3),
+ 10
+ );
+ arrivalDate.setMinutes(minutes);
+ let hours = Number.parseInt(arriveTime.substring(0, arriveTime.indexOf(':')), 10);
+
+ // if hour === 12 and it is am, set hour to zero since it is 12am hour
+ if (hours === 12 && arriveTime.endsWith('AM')) hours = 0;
+
+ arrivalDate.setHours(hours);
+
+ // if it is currently pm and the arrival time is in the am
+ if (new Date().getHours() >= 12 && arriveTime.endsWith('AM')) {
+ // add 24 hours to make it be the next day
+ arrivalDate.setTime(arrivalDate.getTime() + 24 * 60 * 60 * 1000);
+ }
+ // if the given time is in the PM but not in the hour of 12, increase by 12 hours to make date pm
+ else if (hours < 12 && arriveTime.endsWith('PM')) {
+ arrivalDate.setTime(arrivalDate.getTime() + 12 * 60 * 60 * 1000);
+ }
+
+ // get minutes between calculated arrival date and current date
+ return Math.round((arrivalDate - new Date()) / 1000 / 60);
+};
+
+export default UpcomingArrivals;
diff --git a/src/components/icons/FavoriteStopImage.js b/src/components/icons/FavoriteStopImage.js
new file mode 100644
index 0000000..7ba5a00
--- /dev/null
+++ b/src/components/icons/FavoriteStopImage.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import Svg, { Path } from 'react-native-svg';
+
+const FavoriteStopImage = (props) => (
+
+);
+
+export default React.memo(FavoriteStopImage);
diff --git a/src/components/icons/RegularStopImage.js b/src/components/icons/RegularStopImage.js
new file mode 100644
index 0000000..c35432a
--- /dev/null
+++ b/src/components/icons/RegularStopImage.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import Svg, { Path } from 'react-native-svg';
+
+const RegularStopImage = (props) => (
+
+);
+
+export default React.memo(RegularStopImage);
diff --git a/state/atoms.js b/src/state/atoms.js
similarity index 95%
rename from state/atoms.js
rename to src/state/atoms.js
index 3cfa907..e2bda57 100644
--- a/state/atoms.js
+++ b/src/state/atoms.js
@@ -1,5 +1,6 @@
import { atom } from 'recoil';
+import { FAVORITE_ROUTES } from './constants';
import { localForageEffectSet, localForageEffect } from './utilities/localforage/updateEffects';
export const routesState = atom({
@@ -15,7 +16,7 @@ export const vehicleLocationState = atom({
export const currentRouteRowState = atom({
key: 'currentRouteRowState',
- default: -2,
+ default: FAVORITE_ROUTES,
});
export const dispatcherState = atom({
diff --git a/src/state/constants.js b/src/state/constants.js
new file mode 100644
index 0000000..431b999
--- /dev/null
+++ b/src/state/constants.js
@@ -0,0 +1,2 @@
+export const ALL_ROUTES = -1;
+export const FAVORITE_ROUTES = -2;
diff --git a/state/dispatcher.js b/src/state/dispatcher.js
similarity index 100%
rename from state/dispatcher.js
rename to src/state/dispatcher.js
diff --git a/state/dispatchers/favoriteStops.js b/src/state/dispatchers/favoriteStops.js
similarity index 88%
rename from state/dispatchers/favoriteStops.js
rename to src/state/dispatchers/favoriteStops.js
index b0504b9..71a0467 100644
--- a/state/dispatchers/favoriteStops.js
+++ b/src/state/dispatchers/favoriteStops.js
@@ -1,4 +1,5 @@
import { currentRouteRowState, favoriteStopsState } from '../atoms';
+import { ALL_ROUTES } from '../constants';
import { getSetFromLocalStorage } from '../utilities/localforage/getFromLocalStorage';
export const removeFavoriteStop =
@@ -24,8 +25,8 @@ export const fetchFavoriteStops =
async () => {
const favoritesArray = await getSetFromLocalStorage('favorite-stops');
set(favoriteStopsState, favoritesArray);
- if (favoritesArray.size == 0) {
- set(currentRouteRowState, -1);
+ if (favoritesArray.size === 0) {
+ set(currentRouteRowState, ALL_ROUTES);
}
};
diff --git a/state/dispatchers/favorites.js b/src/state/dispatchers/favorites.js
similarity index 100%
rename from state/dispatchers/favorites.js
rename to src/state/dispatchers/favorites.js
diff --git a/state/dispatchers/routes.js b/src/state/dispatchers/routes.js
similarity index 100%
rename from state/dispatchers/routes.js
rename to src/state/dispatchers/routes.js
diff --git a/state/dispatchers/stops.js b/src/state/dispatchers/stops.js
similarity index 100%
rename from state/dispatchers/stops.js
rename to src/state/dispatchers/stops.js
diff --git a/state/dispatchers/userLocation.js b/src/state/dispatchers/userLocation.js
similarity index 100%
rename from state/dispatchers/userLocation.js
rename to src/state/dispatchers/userLocation.js
diff --git a/state/dispatchers/userSettings.js b/src/state/dispatchers/userSettings.js
similarity index 100%
rename from state/dispatchers/userSettings.js
rename to src/state/dispatchers/userSettings.js
diff --git a/state/dispatchers/vehicles.js b/src/state/dispatchers/vehicles.js
similarity index 100%
rename from state/dispatchers/vehicles.js
rename to src/state/dispatchers/vehicles.js
diff --git a/state/selectors.js b/src/state/selectors.js
similarity index 92%
rename from state/selectors.js
rename to src/state/selectors.js
index d933d04..45101bc 100644
--- a/state/selectors.js
+++ b/src/state/selectors.js
@@ -9,6 +9,7 @@ import {
stopsState,
upcomingArrivalsState,
} from './atoms';
+import { ALL_ROUTES } from './constants';
export const currentRoute = selector({
key: 'currentRoute',
@@ -111,7 +112,7 @@ export const currentRouteStopDetailsState = selector({
const currentRouteID = get(currentRouteRowState);
const allRoutes = get(routesState);
- if (currentRouteID === -1) {
+ if (currentRouteID === ALL_ROUTES) {
return Object.values(allStops);
}
@@ -130,3 +131,11 @@ export const currentRouteStopDetailsState = selector({
return result;
},
});
+
+export const isIndividualRoute = selector({
+ key: 'isIndividualRouteSelector',
+ get: ({ get }) => {
+ const currentRouteID = get(currentRouteRowState);
+ return currentRouteID >= 0;
+ },
+});
diff --git a/state/utilities/localforage/getFromLocalStorage.js b/src/state/utilities/localforage/getFromLocalStorage.js
similarity index 100%
rename from state/utilities/localforage/getFromLocalStorage.js
rename to src/state/utilities/localforage/getFromLocalStorage.js
diff --git a/state/utilities/localforage/updateEffects.js b/src/state/utilities/localforage/updateEffects.js
similarity index 100%
rename from state/utilities/localforage/updateEffects.js
rename to src/state/utilities/localforage/updateEffects.js
diff --git a/state/utilities/network/jsonFetch.js b/src/state/utilities/network/jsonFetch.js
similarity index 100%
rename from state/utilities/network/jsonFetch.js
rename to src/state/utilities/network/jsonFetch.js
diff --git a/state/utilities/request/getArrivals.js b/src/state/utilities/request/getArrivals.js
similarity index 100%
rename from state/utilities/request/getArrivals.js
rename to src/state/utilities/request/getArrivals.js
diff --git a/state/utilities/request/getRoutes.js b/src/state/utilities/request/getRoutes.js
similarity index 100%
rename from state/utilities/request/getRoutes.js
rename to src/state/utilities/request/getRoutes.js
diff --git a/state/utilities/request/getStops.js b/src/state/utilities/request/getStops.js
similarity index 100%
rename from state/utilities/request/getStops.js
rename to src/state/utilities/request/getStops.js
diff --git a/state/utilities/request/getVehicleLocations.js b/src/state/utilities/request/getVehicleLocations.js
similarity index 100%
rename from state/utilities/request/getVehicleLocations.js
rename to src/state/utilities/request/getVehicleLocations.js
diff --git a/state/utilities/request/getWaypoints.js b/src/state/utilities/request/getWaypoints.js
similarity index 100%
rename from state/utilities/request/getWaypoints.js
rename to src/state/utilities/request/getWaypoints.js